wbcli.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/* EXPORT DELETE START */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/salib.h>
#include <sys/promif.h>
#include <sys/wanboot_impl.h>
#include <netinet/in.h>
#include <parseURL.h>
#include <bootlog.h>
#include <sys/socket.h>
#include <netinet/inetutil.h>
#include <netinet/dhcp.h>
#include <dhcp_impl.h>
#include <lib/inet/mac.h>
#include <lib/inet/ipv4.h>
#include <lib/inet/dhcpv4.h>
#include <lib/sock/sock_test.h>
#include <sys/sunos_dhcp_class.h>
#include <aes.h>
#include <des3.h>
#include <hmac_sha1.h>
#include <netdb.h>
#include <wanboot_conf.h>
#include <bootinfo.h>
/* EXPORT DELETE END */
#include "wbcli.h"
/* EXPORT DELETE START */
#define skipspace(p) while (isspace(*(p))) ++p
#define skiptext(p) while (*(p) != '\0' && !isspace(*(p)) && \
*(p) != '=' && *(p) != ',') ++p
#define PROMPT "boot> "
#define TEST_PROMPT "boot-test> "
#define CLI_SET 0
#define CLI_FAIL (-1)
#define CLI_EXIT (-2)
#define CLI_CONT (-3)
#define CLF_CMD 0x00000001 /* builtin command */
#define CLF_ARG 0x00000002 /* boot argument directive */
#define CLF_IF 0x00000100 /* interface parameter */
#define CLF_BM 0x00000200 /* bootmisc parameter */
#define CLF_VALSET 0x00010000 /* value set, may be null */
#define CLF_HIDDEN 0x00020000 /* don't show its value (key) */
#define CLF_VALMOD 0x00040000 /* value modified by the user */
/*
* Macros for use in managing the flags in the cli_list[].
* The conventions we follow are:
*
* CLF_VALSET is cleared if a value is removed from varptr
* CLF_VALSET is set if a value has been placed in varptr
* (that value need not be vetted)
* CLF_HIDDEN is set if a value must not be exposed to the user
* CLF_HIDDEN is cleared if a value can be exposed to the user
* CLF_VALMOD is cleared if a value in varptr has not been modified
* CLF_VALMOD is set if a value in varptr has been modified by
* the user
*/
#ifdef DEBUG
#define CLF_SETVAL(var) { \
(((var)->flags) |= CLF_VALSET); \
printf("set %s\n", var->varname);\
}
#define CLF_ISSET(var) (printf("%s\n", \
(((var)->flags) & CLF_VALSET) != 0 \
? "is set" : "not set"), \
((((var)->flags) & CLF_VALSET) != 0))
#define CLF_CLRHIDDEN(var) { \
(((var)->flags) &= ~CLF_HIDDEN); \
printf("unhide %s\n", var->varname); \
}
#define CLF_ISHIDDEN(var) (printf("%s\n", \
(((var)->flags) & CLF_HIDDEN) != 0 \
? "is hidden" : "not hidden"), \
((((var)->flags) & CLF_HIDDEN) != 0))
#define CLF_MODVAL(var) { \
(((var)->flags) |= \
(CLF_VALMOD | CLF_VALSET)); \
printf("modified %s\n", var->varname);\
}
#define CLF_ISMOD(var) (printf("%s\n", \
(((var)->flags) & CLF_VALMOD) != 0 \
? "is set" : "not set"), \
((((var)->flags) & CLF_VALMOD) != 0))
#else /* DEBUG */
#define CLF_SETVAL(var) (((var)->flags) |= CLF_VALSET)
#define CLF_ISSET(var) ((((var)->flags) & CLF_VALSET) != 0)
#define CLF_CLRHIDDEN(var) (((var)->flags) &= ~CLF_HIDDEN)
#define CLF_ISHIDDEN(var) ((((var)->flags) & CLF_HIDDEN) != 0)
#define CLF_MODVAL(var) (((var)->flags) |= (CLF_VALMOD | CLF_VALSET))
#define CLF_ISMOD(var) ((((var)->flags) & CLF_VALMOD) != 0)
#endif /* DEBUG */
/*
* The width of the widest varname below - currently "subnet_mask".
*/
#define VAR_MAXWIDTH strlen(BI_SUBNET_MASK)
struct cli_ent;
typedef int claction_t(struct cli_ent *, char *, boolean_t);
typedef struct cli_ent {
char *varname;
claction_t *action;
int flags;
void *varptr;
uint_t varlen;
uint_t varmax;
} cli_ent_t;
static cli_ent_t *find_cli_ent(char *varstr);
static char cmdbuf[2048]; /* interpreter buffer */
static char hostip[INET_ADDRSTRLEN];
static char subnet[INET_ADDRSTRLEN];
static char router[INET_ADDRSTRLEN];
static char hostname[MAXHOSTNAMELEN];
static char httpproxy[INET_ADDRSTRLEN + 5]; /* a.b.c.d:p */
static char bootserverURL[URL_MAX_STRLEN + 1];
static unsigned char clientid[WB_MAX_CID_LEN];
static unsigned char aeskey[AES_128_KEY_SIZE];
static unsigned char des3key[DES3_KEY_SIZE];
static unsigned char sha1key[WANBOOT_HMAC_KEY_SIZE];
static boolean_t args_specified_prompt = B_FALSE;
extern bc_handle_t bc_handle;
extern int getchar(void);
static claction_t clcid, clkey, clip, clstr, clurl, clhp;
static claction_t clhelp, cllist, clprompt, cldhcp, cltest, clgo, clexit;
static cli_ent_t cli_list[] = {
/*
* Commands/bootargs:
*/
{ "test", cltest, CLF_ARG,
NULL, 0, 0 },
{ "dhcp", cldhcp, CLF_ARG,
NULL, 0, 0 },
{ "prompt", clprompt, CLF_CMD | CLF_ARG,
NULL, 0, 0 },
{ "list", cllist, CLF_CMD,
NULL, 0, 0 },
{ "help", clhelp, CLF_CMD,
NULL, 0, 0 },
{ "go", clgo, CLF_CMD,
NULL, 0, 0 },
{ "exit", clexit, CLF_CMD,
NULL, 0, 0 },
/*
* Interface:
*/
{ BI_HOST_IP, clip, CLF_IF,
hostip, 0, sizeof (hostip) },
{ BI_SUBNET_MASK, clip, CLF_IF,
subnet, 0, sizeof (subnet) },
{ BI_ROUTER_IP, clip, CLF_IF,
router, 0, sizeof (router) },
{ BI_HOSTNAME, clstr, CLF_IF,
hostname, 0, sizeof (hostname) },
{ BI_HTTP_PROXY, clhp, CLF_IF,
httpproxy, 0, sizeof (httpproxy) },
{ BI_CLIENT_ID, clcid, CLF_IF,
clientid, 0, sizeof (clientid) },
/*
* Bootmisc:
*/
{ BI_AES_KEY, clkey, CLF_BM | CLF_HIDDEN,
aeskey, 0, sizeof (aeskey) },
{ BI_3DES_KEY, clkey, CLF_BM | CLF_HIDDEN,
des3key, 0, sizeof (des3key) },
{ BI_SHA1_KEY, clkey, CLF_BM | CLF_HIDDEN,
sha1key, 0, sizeof (sha1key) },
{ BI_BOOTSERVER, clurl, CLF_BM,
bootserverURL, 0, sizeof (bootserverURL) },
};
static int num_cli_ent = (sizeof (cli_list) / sizeof (cli_ent_t));
/*
* Fetch a line from the user, handling backspace appropriately.
*/
static int
editline(char *buf, int count)
{
int i = 0;
char c;
while (i < count - 1) {
c = getchar();
if (c == '\n') {
break;
} else if (c == '\b') {
/* Clear for backspace. */
if (i > 0)
i--;
continue;
} else {
buf[i++] = c;
}
}
buf[i] = '\0';
return (i);
}
/*
* Assign a client-id to cliptr, or output cliptr's value as a client-id.
* On assignment the value is specified in valstr, either in hexascii or
* as a quoted string; on output its value is printed in hexascii.
*/
static int
clcid(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
uint_t len, vmax;
boolean_t hexascii = B_TRUE;
char buffer[2 * WB_MAX_CID_LEN + 1];
if (out) {
len = cliptr->varlen * 2 + 1;
(void) octet_to_hexascii(cliptr->varptr, cliptr->varlen,
buffer, &len);
printf("%s", buffer);
return (CLI_CONT);
} else {
len = strlen(valstr);
vmax = cliptr->varmax - 1; /* space for the prefix */
/*
* Check whether the value is a quoted string; if so, strip
* the quotes and note that it's not in hexascii.
*/
if ((valstr[0] == '"' || valstr[0] == '\'') &&
valstr[len-1] == valstr[0]) {
hexascii = B_FALSE;
++valstr;
len -= 2;
valstr[len] = '\0';
} else {
/*
* If the value contains any non-hex digits assume
* that it's not in hexascii.
*/
char *p;
for (p = valstr; *p != '\0'; ++p) {
if (!isxdigit(*p)) {
hexascii = B_FALSE;
break;
}
}
}
if (hexascii) {
if (len > vmax * 2 ||
hexascii_to_octet(valstr, len,
(char *)(cliptr->varptr), &vmax) != 0) {
return (CLI_FAIL);
}
cliptr->varlen = vmax;
} else {
if (len > vmax) {
return (CLI_FAIL);
}
bcopy(valstr, cliptr->varptr, len);
cliptr->varlen = len;
}
return (CLI_SET);
}
}
/*
* Assign a key to cliptr, or output cliptr's value as a key.
* On assignment the value is specified in valstr in hexascii;
* on output its value is printed in hexascii, provided the key
* was entered at the interpreter (not obtained from OBP and
* thus hidden).
*/
static int
clkey(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
uint_t len, vmax;
if (out) {
char buffer[2 * WANBOOT_MAXKEYLEN + 1];
if (!CLF_ISHIDDEN(cliptr)) {
len = cliptr->varlen * 2 + 1;
(void) octet_to_hexascii(cliptr->varptr,
cliptr->varlen, buffer, &len);
printf("%s", buffer);
} else {
printf("*HIDDEN*");
}
return (CLI_CONT);
} else {
len = strlen(valstr);
vmax = cliptr->varmax;
if (len != vmax * 2 || hexascii_to_octet(valstr, len,
cliptr->varptr, &vmax) != 0) {
return (CLI_FAIL);
}
cliptr->varlen = vmax;
CLF_CLRHIDDEN(cliptr);
return (CLI_SET);
}
}
/*
* Assign an IP address to cliptr, or output cliptr's value as an
* IP address. On assignment the value is specified in valstr in
* dotted-decimal format; on output its value is printed in dotted-
* decimal format.
*/
static int
clip(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
uint_t len;
if (out) {
printf("%s", (char *)cliptr->varptr);
return (CLI_CONT);
}
if (inet_addr(valstr) == (in_addr_t)-1 ||
(len = strlen(valstr)) >= cliptr->varmax) {
return (CLI_FAIL);
}
(void) strcpy(cliptr->varptr, valstr);
cliptr->varlen = len + 1;
return (CLI_SET);
}
/*
* Assign an arbitrary string to cliptr, or output cliptr's value as a string.
*/
static int
clstr(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
uint_t len;
if (out) {
printf("%s", (char *)cliptr->varptr);
return (CLI_CONT);
} else {
if ((len = strlen(valstr)) >= cliptr->varmax) {
return (CLI_FAIL);
} else {
(void) strcpy(cliptr->varptr, valstr);
cliptr->varlen = len + 1;
return (CLI_SET);
}
}
}
/*
* Assign a URL to cliptr (having verified the format), or output cliptr's
* value as a URL. The host must be specified in dotted-decimal, and the
* scheme must not be https.
*/
static int
clurl(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
url_t u;
uint_t len;
if (out) {
printf("%s", (char *)cliptr->varptr);
return (CLI_CONT);
}
if (url_parse(valstr, &u) != URL_PARSE_SUCCESS ||
u.https || inet_addr(u.hport.hostname) == (in_addr_t)-1 ||
(len = strlen(valstr)) >= cliptr->varmax) {
return (CLI_FAIL);
}
(void) strcpy(cliptr->varptr, valstr);
cliptr->varlen = len + 1;
return (CLI_SET);
}
/*
* Assign a hostport to cliptr (having verified the format), or output cliptr's
* value as a hostport. The host must be specified in dotted-decimal.
*/
static int
clhp(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
url_hport_t u;
uint_t len;
if (out) {
printf("%s", (char *)cliptr->varptr);
return (CLI_CONT);
}
if (url_parse_hostport(valstr, &u, URL_DFLT_PROXY_PORT) !=
URL_PARSE_SUCCESS ||
inet_addr(u.hostname) == (in_addr_t)-1 ||
(len = strlen(valstr)) >= cliptr->varmax) {
return (CLI_FAIL);
}
(void) strcpy(cliptr->varptr, valstr);
cliptr->varlen = len + 1;
return (CLI_SET);
}
/*
* Exit the interpreter and return to the booter.
*/
/*ARGSUSED*/
static int
clgo(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
return (CLI_EXIT);
}
/*
* Exit the interpreter and return to OBP.
*/
/*ARGSUSED*/
static int
clexit(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
prom_exit_to_mon();
/*NOTREACHED*/
return (CLI_EXIT);
}
/*
* Provide simple help information.
*/
/*ARGSUSED*/
static int
clhelp(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
printf("var=val - set variable\n");
printf("var= - unset variable\n");
printf("var - print variable\n");
printf("list - list variables and their values\n");
printf("prompt - prompt for unset variables\n");
printf("go - continue booting\n");
printf("exit - quit boot interpreter and return to OBP\n");
return (CLI_CONT);
}
/*
* List variables and their current values.
*/
/*ARGSUSED*/
static int
cllist(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
int wanted = (int)valstr;
int i;
wanted &= ~(CLF_CMD | CLF_ARG);
for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; cliptr++) {
if ((cliptr->flags & (CLF_CMD | CLF_ARG)) != 0 ||
(cliptr->flags & wanted) == 0) {
continue;
}
printf("%s: ", cliptr->varname);
/*
* Line the values up - space to the width of the widest
* varname + 1 for the ':'.
*/
for (i = VAR_MAXWIDTH + 1 - strlen(cliptr->varname);
i > 0; --i) {
printf(" ");
}
if (CLF_ISSET(cliptr) || CLF_ISHIDDEN(cliptr)) {
(void) cliptr->action(cliptr, NULL, B_TRUE);
printf("\n");
} else {
printf("UNSET\n");
}
}
return (CLI_CONT);
}
/*
* Prompt for wanted values.
*/
/*ARGSUSED*/
static int
clprompt(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
char *p;
int wanted = (int)valstr;
/*
* If processing boot arguments, simply note the fact that clprompt()
* should be invoked later when other parameters may be supplied.
*/
if ((wanted & CLF_ARG) != 0) {
args_specified_prompt = B_TRUE;
return (CLI_CONT);
}
wanted &= ~(CLF_CMD | CLF_ARG);
for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; ++cliptr) {
if ((cliptr->flags & wanted) == 0) {
continue;
}
printf("%s", cliptr->varname);
if (CLF_ISSET(cliptr)) {
printf(" [");
(void) cliptr->action(cliptr, NULL, B_TRUE);
printf("]");
}
printf("? ");
(void) editline(cmdbuf, sizeof (cmdbuf));
printf("\n");
p = cmdbuf;
skipspace(p);
if (*p == '\0') { /* nothing there */
continue;
}
/* Get valstr and nul terminate */
valstr = p;
++p;
skiptext(p);
*p = '\0';
/* If empty value, do nothing */
if (strlen(valstr) == 0) {
continue;
}
switch (cliptr->action(cliptr, valstr, B_FALSE)) {
case CLI_SET:
CLF_MODVAL(cliptr);
break;
case CLI_FAIL:
printf("Incorrect format, parameter unchanged!\n");
break;
case CLI_EXIT:
return (CLI_EXIT);
case CLI_CONT:
break;
}
}
return (CLI_CONT);
}
/*
* If the PROM has done DHCP, bind the interface; otherwise do the full
* DHCP packet exchange.
*/
/*ARGSUSED*/
static int
cldhcp(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
static boolean_t first_time = B_TRUE;
static int ret = CLI_CONT;
if (first_time) {
/*
* Set DHCP's idea of the client_id from our cached value.
*/
cliptr = find_cli_ent(BI_CLIENT_ID);
if (CLF_ISMOD(cliptr)) {
dhcp_set_client_id(cliptr->varptr, cliptr->varlen);
}
bootlog("wanboot", BOOTLOG_INFO, "Starting DHCP configuration");
(void) ipv4_setpromiscuous(B_TRUE);
if (dhcp() == 0) {
bootlog("wanboot", BOOTLOG_INFO,
"DHCP configuration succeeded");
} else {
bootlog("wanboot", BOOTLOG_CRIT,
"DHCP configuration failed");
ret = CLI_FAIL;
}
(void) ipv4_setpromiscuous(B_FALSE);
first_time = B_FALSE;
}
return (ret);
}
/*
* Invoke the socket test interpreter (for testing purposes only).
*/
/*ARGSUSED*/
static int
cltest(cli_ent_t *cliptr, char *valstr, boolean_t out)
{
(void) ipv4_setpromiscuous(B_FALSE);
printf("\n");
for (;;) {
printf(TEST_PROMPT);
if (editline(cmdbuf, sizeof (cmdbuf)) > 0) {
printf("\n");
(void) st_interpret(cmdbuf);
} else {
prom_exit_to_mon();
/* NOTREACHED */
}
}
/* NOTREACHED */
return (CLI_CONT);
}
/*
* Return the cliptr corresponding to the named variable.
*/
static cli_ent_t *
find_cli_ent(char *varstr)
{
cli_ent_t *cliptr;
for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; ++cliptr) {
if (strcmp(varstr, cliptr->varname) == 0) {
return (cliptr);
}
}
return (NULL);
}
/*
* Evaluate the commands provided by the user (either as "-o" boot arguments
* or interactively at the boot interpreter).
*/
static int
cli_eval_buf(char *inbuf, int wanted)
{
char *p, *varstr, *end_varstr, *valstr, *end_valstr;
boolean_t assign;
cli_ent_t *cliptr;
for (p = inbuf; *p != '\0'; ) {
skipspace(p);
/* If nothing more on line, go get the next one */
if (*p == '\0') {
break;
} else if (*p == ',') { /* orphan ',' ? */
++p;
continue;
}
/* Get ptrs to start & end of variable */
varstr = p;
++p;
skiptext(p);
end_varstr = p;
skipspace(p);
/* See if we're doing an assignment */
valstr = NULL;
if (*p != '=') { /* nope, just printing */
assign = B_FALSE;
} else {
assign = B_TRUE;
++p; /* past '=' */
skipspace(p);
/* Assigning something? (else clear variable) */
if (*p != '\0' && *p != ',') {
/* Get ptrs to start & end of valstr */
valstr = p;
++p;
skiptext(p);
end_valstr = p;
skipspace(p);
}
}
/* Skip ',' delimiter if present */
if (*p == ',') {
++p;
}
/* Nul-terminate varstr and valstr (if appropriate) */
*end_varstr = '\0';
if (valstr != NULL) {
*end_valstr = '\0';
}
if ((cliptr = find_cli_ent(varstr)) == NULL) {
printf("Unknown variable '%s'; ignored\n", varstr);
continue;
}
/*
* It's an error to specify a parameter which can only be a
* boot argument (and not a command) when not processing the
* boot arguments.
*/
if ((cliptr->flags & (CLF_CMD | CLF_ARG)) == CLF_ARG &&
(wanted & CLF_ARG) == 0) {
printf("'%s' may only be specified as a "
"boot argument; ignored\n", varstr);
continue;
}
/*
* When doing an assignment, verify that it's not a command
* or argument name, and that it is permissible in the current
* context. An 'empty' assignment (var=) is treated the same
* as a null assignment (var="").
*
* If processing the boot arguments, it is an error to not
* assign a value to a non-argument parameter.
*/
if (assign) {
if ((cliptr->flags & (CLF_CMD | CLF_ARG)) != 0) {
printf("'%s' is a command and cannot "
"be assigned\n", varstr);
return (CLI_FAIL);
}
if ((cliptr->flags & wanted) == 0) {
printf("'%s' cannot be assigned\n", varstr);
return (CLI_FAIL);
}
if (valstr == NULL) {
cliptr->varlen = 0;
CLF_MODVAL(cliptr);
continue;
}
} else if ((wanted & CLF_ARG) != 0 &&
(cliptr->flags & (CLF_CMD | CLF_ARG)) == 0) {
printf("'%s' must be assigned when specified in "
" the boot arguments\n", varstr);
return (CLI_FAIL);
}
/*
* Pass 'wanted' to command-handling functions, in particular
* clprompt() and cllist().
*/
if ((cliptr->flags & CLF_CMD) != 0) {
valstr = (char *)wanted;
}
/*
* Call the parameter's action function.
*/
switch (cliptr->action(cliptr, valstr, !assign)) {
case CLI_SET:
CLF_MODVAL(cliptr);
break;
case CLI_FAIL:
printf("Incorrect format: variable '%s' not set\n",
cliptr->varname);
break;
case CLI_EXIT:
return (CLI_EXIT);
case CLI_CONT:
if (!assign) {
printf("\n");
}
break;
}
}
return (CLI_CONT);
}
static void
cli_interpret(int wanted)
{
printf("\n");
do {
printf(PROMPT);
(void) editline(cmdbuf, sizeof (cmdbuf));
printf("\n");
} while (cli_eval_buf(cmdbuf, wanted) != CLI_EXIT);
}
#if defined(__sparcv9)
/*
* This routine queries the PROM to see what encryption keys exist.
*/
static void
get_prom_encr_keys()
{
cli_ent_t *cliptr;
char encr_key[WANBOOT_MAXKEYLEN];
int keylen;
int status;
int ret;
/*
* At the top of the priority list, we have AES.
*/
ret = prom_get_security_key(WANBOOT_AES_128_KEY_NAME, encr_key,
WANBOOT_MAXKEYLEN, &keylen, &status);
if ((ret == 0) && (status == 0) && (keylen == AES_128_KEY_SIZE)) {
cliptr = find_cli_ent(BI_AES_KEY);
bcopy(encr_key, cliptr->varptr, AES_128_KEY_SIZE);
cliptr->varlen = AES_128_KEY_SIZE;
CLF_MODVAL(cliptr);
}
/*
* Next, 3DES.
*/
ret = prom_get_security_key(WANBOOT_DES3_KEY_NAME, encr_key,
WANBOOT_MAXKEYLEN, &keylen, &status);
if ((ret == 0) && (status == 0) && (keylen == DES3_KEY_SIZE)) {
cliptr = find_cli_ent(BI_3DES_KEY);
bcopy(encr_key, cliptr->varptr, DES3_KEY_SIZE);
cliptr->varlen = DES3_KEY_SIZE;
CLF_MODVAL(cliptr);
}
}
/*
* This routine queries the PROM to see what hashing keys exist.
*/
static void
get_prom_hash_keys()
{
cli_ent_t *cliptr;
char hash_key[WANBOOT_HMAC_KEY_SIZE];
int keylen;
int status;
int ret;
/*
* The only supported key thus far is SHA1.
*/
ret = prom_get_security_key(WANBOOT_HMAC_SHA1_KEY_NAME, hash_key,
WANBOOT_HMAC_KEY_SIZE, &keylen, &status);
if ((ret == 0) && (status == 0) && (keylen == WANBOOT_HMAC_KEY_SIZE)) {
cliptr = find_cli_ent(BI_SHA1_KEY);
bcopy(hash_key, cliptr->varptr, WANBOOT_HMAC_KEY_SIZE);
cliptr->varlen = WANBOOT_HMAC_KEY_SIZE;
CLF_MODVAL(cliptr);
}
}
#endif /* defined(__sparcv9) */
/*
* For the given parameter type(s), get values from bootinfo and cache in
* the local variables used by the "boot>" interpreter.
*/
static void
bootinfo_defaults(int which)
{
cli_ent_t *cliptr;
for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; ++cliptr) {
if ((cliptr->flags & which) != 0 && !CLF_ISSET(cliptr)) {
size_t len = cliptr->varmax;
if (bootinfo_get(cliptr->varname, cliptr->varptr,
&len, NULL) == BI_E_SUCCESS) {
cliptr->varlen = len;
CLF_SETVAL(cliptr);
}
}
}
}
/*
* For the given parameter type(s), store values entered at the "boot>"
* interpreter back into bootinfo.
*/
static void
update_bootinfo(int which)
{
cli_ent_t *cliptr;
for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; ++cliptr) {
if ((cliptr->flags & which) != 0 && CLF_ISMOD(cliptr)) {
(void) bootinfo_put(cliptr->varname,
cliptr->varptr, cliptr->varlen, 0);
}
}
}
/*
* Return the net-config-strategy: "dhcp", "manual" or "rarp"
*/
static char *
net_config_strategy(void)
{
static char ncs[8]; /* "dhcp" or "manual" */
size_t len = sizeof (ncs);
if (ncs[0] == '\0' &&
bootinfo_get(BI_NET_CONFIG_STRATEGY, ncs, &len, NULL) !=
BI_E_SUCCESS) {
/*
* Support for old PROMs: create the net-config-strategy
* property under /chosen with an appropriate value. If we
* have a bootp-response (not interested in its value, just
* its presence) then we did DHCP; otherwise configuration
* is manual.
*/
if (bootinfo_get(BI_BOOTP_RESPONSE, NULL, NULL,
NULL) == BI_E_BUF2SMALL) {
(void) strcpy(ncs, "dhcp");
} else {
(void) strcpy(ncs, "manual");
}
(void) bootinfo_put(BI_NET_CONFIG_STRATEGY, ncs, strlen(ncs),
BI_R_CHOSEN);
bootlog("wanboot", BOOTLOG_INFO,
"Default net-config-strategy: %s", ncs);
}
return (ncs);
}
/*
* If there is no client-id property published in /chosen (by the PROM or the
* boot interpreter) provide a default client-id based on the MAC address of
* the client.
* As specified in RFC2132 (section 9.14), this is prefixed with a byte
* which specifies the ARP hardware type defined in RFC1700 (for Ethernet,
* this should be 1).
*/
static void
generate_default_clientid(void)
{
char clid[WB_MAX_CID_LEN];
size_t len = sizeof (clid);
if (bootinfo_get(BI_CLIENT_ID, clid, &len, NULL) != BI_E_SUCCESS) {
len = mac_get_addr_len() + 1; /* include hwtype */
if (len > sizeof (clid)) {
return;
}
clid[0] = mac_arp_type(mac_get_type());
bcopy(mac_get_addr_buf(), &clid[1], len - 1);
(void) bootinfo_put(BI_CLIENT_ID, clid, len, 0);
}
}
/*
* Determine the URL of the boot server from the 'file' parameter to OBP,
* the SbootURI or BootFile DHCP options, or the 'bootserver' value entered
* either as a "-o" argument or at the interpreter.
*/
static void
determine_bootserver_url(void)
{
char bs[URL_MAX_STRLEN + 1];
size_t len;
url_t url;
if (bootinfo_get(BI_BOOTSERVER, bs, &len, NULL) != BI_E_SUCCESS) {
/*
* If OBP has published a network-boot-file property in
* /chosen (or there is a DHCP BootFile or SbootURI vendor
* option) and it's a URL, construct the bootserver URL
* from it.
*/
len = URL_MAX_STRLEN;
if (bootinfo_get(BI_NETWORK_BOOT_FILE, bs, &len, NULL) !=
BI_E_SUCCESS) {
len = URL_MAX_STRLEN;
if (bootinfo_get(BI_BOOTFILE, bs, &len, NULL) !=
BI_E_SUCCESS) {
return;
}
}
if (url_parse(bs, &url) == URL_PARSE_SUCCESS) {
(void) bootinfo_put(BI_BOOTSERVER, bs, len, 0);
}
}
}
/*
* Provide a classful subnet mask based on the client's IP address.
*/
static in_addr_t
generate_classful_subnet(in_addr_t client_ipaddr)
{
struct in_addr subnetmask;
char *netstr;
if (IN_CLASSA(client_ipaddr)) {
subnetmask.s_addr = IN_CLASSA_NET;
} else if (IN_CLASSB(client_ipaddr)) {
subnetmask.s_addr = IN_CLASSB_NET;
} else {
subnetmask.s_addr = IN_CLASSC_NET;
}
netstr = inet_ntoa(subnetmask);
(void) bootinfo_put(BI_SUBNET_MASK, netstr, strlen(netstr) + 1, 0);
return (subnetmask.s_addr);
}
/*
* Informational output to the user (if interactive) or the bootlogger.
*/
static void
info(const char *msg, boolean_t interactive)
{
if (interactive) {
printf("%s\n", msg);
} else {
bootlog("wanboot", BOOTLOG_INFO, "%s", msg);
}
}
/*
* Determine whether we have sufficient information to proceed with booting,
* either for configuring the interface and downloading the bootconf file,
* or for downloading the miniroot.
*/
static int
config_incomplete(int why, boolean_t interactive)
{
boolean_t error = B_FALSE;
char buf[URL_MAX_STRLEN + 1];
size_t len;
char *urlstr;
url_t u;
struct hostent *hp;
in_addr_t client_ipaddr, ipaddr, bsnet, pxnet;
static in_addr_t subnetmask, clnet;
static boolean_t have_router = B_FALSE;
static boolean_t have_proxy = B_FALSE;
boolean_t have_root_server = B_FALSE;
boolean_t have_boot_logger = B_FALSE;
in_addr_t rsnet, blnet;
/*
* Note that 'have_router', 'have_proxy', 'subnetmask', and 'clnet'
* are static, so that their values (gathered when checking the
* interface configuration) may be used again when checking the boot
* configuration.
*/
if (why == CLF_IF) {
/*
* A valid host IP address is an absolute requirement.
*/
len = sizeof (buf);
if (bootinfo_get(BI_HOST_IP, buf, &len, NULL) == BI_E_SUCCESS) {
if ((client_ipaddr = inet_addr(buf)) == (in_addr_t)-1) {
info("host-ip invalid!", interactive);
error = B_TRUE;
}
} else {
info("host-ip not set!", interactive);
error = B_TRUE;
}
/*
* If a subnet mask was provided, use it; otherwise infer it.
*/
len = sizeof (buf);
if (bootinfo_get(BI_SUBNET_MASK, buf, &len, NULL) ==
BI_E_SUCCESS) {
if ((subnetmask = inet_addr(buf)) == (in_addr_t)-1) {
info("subnet-mask invalid!", interactive);
error = B_TRUE;
}
} else {
info("Defaulting to classful subnetting", interactive);
subnetmask = generate_classful_subnet(client_ipaddr);
}
clnet = client_ipaddr & subnetmask;
/*
* A legal bootserver URL is also an absolute requirement.
*/
len = sizeof (buf);
if (bootinfo_get(BI_BOOTSERVER, buf, &len, NULL) ==
BI_E_SUCCESS) {
if (url_parse(buf, &u) != URL_PARSE_SUCCESS ||
u.https ||
(ipaddr = inet_addr(u.hport.hostname)) ==
(in_addr_t)-1) {
info("bootserver not legal URL!", interactive);
error = B_TRUE;
} else {
bsnet = ipaddr & subnetmask;
}
} else {
info("bootserver not specified!", interactive);
error = B_TRUE;
}
/*
* Is there a correctly-defined router?
*/
len = sizeof (buf);
if (bootinfo_get(BI_ROUTER_IP, buf, &len, NULL) ==
BI_E_SUCCESS) {
if ((ipaddr = inet_addr(buf)) == (in_addr_t)-1) {
info("router-ip invalid!", interactive);
error = B_TRUE;
} else if (clnet != (ipaddr & subnetmask)) {
info("router not on local subnet!",
interactive);
error = B_TRUE;
} else {
have_router = B_TRUE;
}
}
/*
* Is there a correctly-defined proxy?
*/
len = sizeof (buf);
if (bootinfo_get(BI_HTTP_PROXY, buf, &len, NULL) ==
BI_E_SUCCESS) {
url_hport_t u;
if (url_parse_hostport(buf, &u, URL_DFLT_PROXY_PORT) !=
URL_PARSE_SUCCESS ||
(ipaddr = inet_addr(u.hostname)) == (in_addr_t)-1) {
info("http-proxy port invalid!", interactive);
error = B_TRUE;
} else {
/*
* The proxy is only of use to us if it's on
* our local subnet, or if a router has been
* specified (which should hopefully allow us
* to access the proxy).
*/
pxnet = ipaddr & subnetmask;
have_proxy = (have_router || pxnet == clnet);
}
}
/*
* If there is no router and no proxy (either on the local
* subnet or reachable via a router), then the bootserver
* URL must be on the local net.
*/
if (!error && !have_router && !have_proxy && bsnet != clnet) {
info("bootserver URL not on local subnet",
interactive);
error = B_TRUE;
}
} else {
/*
* There must be a correctly-defined root_server URL.
*/
if ((urlstr = bootconf_get(&bc_handle,
BC_ROOT_SERVER)) == NULL) {
info("no root_server URL!", interactive);
error = B_TRUE;
} else if (url_parse(urlstr, &u) != URL_PARSE_SUCCESS) {
info("root_server not legal URL!", interactive);
error = B_TRUE;
} else if ((hp = gethostbyname(u.hport.hostname)) == NULL) {
info("cannot resolve root_server hostname!",
interactive);
error = B_TRUE;
} else {
rsnet = *(in_addr_t *)hp->h_addr & subnetmask;
have_root_server = B_TRUE;
}
/*
* Is there a correctly-defined (non-empty) boot_logger URL?
*/
if ((urlstr = bootconf_get(&bc_handle,
BC_BOOT_LOGGER)) != NULL) {
if (url_parse(urlstr, &u) != URL_PARSE_SUCCESS) {
info("boot_logger not legal URL!", interactive);
error = B_TRUE;
} else if ((hp = gethostbyname(u.hport.hostname)) ==
NULL) {
info("cannot resolve boot_logger hostname!",
interactive);
error = B_TRUE;
} else {
blnet = *(in_addr_t *)hp->h_addr & subnetmask;
have_boot_logger = B_TRUE;
}
}
/*
* If there is no router and no proxy (either on the local
* subnet or reachable via a router), then the root_server
* URL (and the boot_logger URL if specified) must be on the
* local net.
*/
if (!error && !have_router && !have_proxy) {
if (have_root_server && rsnet != clnet) {
info("root_server URL not on local subnet",
interactive);
error = B_TRUE;
}
if (have_boot_logger && blnet != clnet) {
info("boot_logger URL not on local subnet",
interactive);
error = B_TRUE;
}
}
}
return (error);
}
/*
* Actually setup our network interface with the values derived from the
* PROM, DHCP or interactively from the user.
*/
static void
setup_interface()
{
char str[MAXHOSTNAMELEN]; /* will accomodate an IP too */
size_t len;
struct in_addr in_addr;
len = sizeof (str);
if (bootinfo_get(BI_HOST_IP, str, &len, NULL) == BI_E_SUCCESS &&
(in_addr.s_addr = inet_addr(str)) != (in_addr_t)-1) {
in_addr.s_addr = htonl(in_addr.s_addr);
ipv4_setipaddr(&in_addr);
}
len = sizeof (str);
if (bootinfo_get(BI_SUBNET_MASK, str, &len, NULL) == BI_E_SUCCESS &&
(in_addr.s_addr = inet_addr(str)) != (in_addr_t)-1) {
in_addr.s_addr = htonl(in_addr.s_addr);
ipv4_setnetmask(&in_addr);
}
len = sizeof (str);
if (bootinfo_get(BI_ROUTER_IP, str, &len, NULL) == BI_E_SUCCESS &&
(in_addr.s_addr = inet_addr(str)) != (in_addr_t)-1) {
in_addr.s_addr = htonl(in_addr.s_addr);
ipv4_setdefaultrouter(&in_addr);
(void) ipv4_route(IPV4_ADD_ROUTE, RT_DEFAULT, NULL, &in_addr);
}
len = sizeof (str);
if (bootinfo_get(BI_HOSTNAME, str, &len, NULL) == BI_E_SUCCESS) {
(void) sethostname(str, len);
}
}
/* EXPORT DELETE END */
boolean_t
wanboot_init_interface(char *boot_arguments)
{
/* EXPORT DELETE START */
boolean_t interactive;
int which;
#if defined(__sparcv9)
/*
* Get the keys from PROM before we allow the user
* to override them from the CLI.
*/
get_prom_encr_keys();
get_prom_hash_keys();
#endif /* defined(__sparcv9) */
/*
* If there is already a bootp-response property under
* /chosen then the PROM must have done DHCP for us;
* invoke dhcp() to 'bind' the interface.
*/
if (bootinfo_get(BI_BOOTP_RESPONSE, NULL, NULL, NULL) ==
BI_E_BUF2SMALL) {
(void) cldhcp(NULL, NULL, 0);
}
/*
* Obtain default interface values from bootinfo.
*/
bootinfo_defaults(CLF_IF);
/*
* Process the boot arguments (following the "-o" option).
*/
if (boot_arguments != NULL) {
(void) cli_eval_buf(boot_arguments,
(CLF_ARG | CLF_IF | CLF_BM));
}
/*
* Stash away any interface/bootmisc parameter values we got
* from either the PROM or the boot arguments.
*/
update_bootinfo(CLF_IF | CLF_BM);
/*
* If we don't already have a value for bootserver, try to
* deduce one. Refresh wbcli's idea of these values.
*/
determine_bootserver_url();
bootinfo_defaults(CLF_BM);
/*
* Check that the information we have collected thus far is sufficient.
*/
interactive = args_specified_prompt;
if (interactive) {
/*
* Drop into the boot interpreter to allow the input
* of keys, bootserver and bootmisc, and in the case
* that net-config-strategy == "manual" the interface
* parameters.
*/
which = CLF_BM | CLF_CMD;
if (strcmp(net_config_strategy(), "manual") == 0)
which |= CLF_IF;
do {
cli_interpret(which);
update_bootinfo(CLF_IF | CLF_BM);
} while (config_incomplete(CLF_IF, interactive));
} else {
/*
* The user is not to be given the opportunity to
* enter further values; fail.
*/
if (config_incomplete(CLF_IF, interactive)) {
bootlog("wanboot", BOOTLOG_CRIT,
"interface incorrectly configured");
return (B_FALSE);
}
}
/*
* If a wanboot-enabled PROM hasn't processed client-id in
* network-boot-arguments, or no value for client-id has been
* specified to the boot interpreter, then provide a default
* client-id based on our MAC address.
*/
generate_default_clientid();
/*
* If net-config-strategy == "manual" then we must setup
* the interface now; if "dhcp" then it will already have
* been setup.
*/
if (strcmp(net_config_strategy(), "manual") == 0)
setup_interface();
/* EXPORT DELETE END */
return (B_TRUE);
}
boolean_t
wanboot_verify_config(void)
{
/* EXPORT DELETE START */
/*
* Check that the wanboot.conf file defines a valid root_server
* URL, and check that, if given, the boot_logger URL is valid.
*/
if (config_incomplete(0, B_FALSE)) {
bootlog("wanboot", BOOTLOG_CRIT,
"incomplete boot configuration");
return (B_FALSE);
}
/* EXPORT DELETE END */
return (B_TRUE);
}