561 lines
12 KiB
C
561 lines
12 KiB
C
/*
|
|
* Copyright (C) 2007-2014 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
|
|
*
|
|
* This document is the property of Apple Computer, Inc.
|
|
* It is considered confidential and proprietary.
|
|
*
|
|
* This document may not be reproduced or transmitted in any form,
|
|
* in whole or in part, without the express written permission of
|
|
* Apple Computer, Inc.
|
|
*/
|
|
#include <debug.h>
|
|
#include <list.h>
|
|
#include <lib/env.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/menu.h>
|
|
|
|
static struct list_node env_list = LIST_INITIAL_VALUE(env_list);
|
|
|
|
#define ENV_VAR_ENTRY_NAME_LEN 64
|
|
|
|
struct env_var_entry {
|
|
struct list_node node;
|
|
struct env_var var;
|
|
char name[ENV_VAR_ENTRY_NAME_LEN];
|
|
};
|
|
|
|
static int str2ip(const char *str, uint32_t *ip);
|
|
static int str2mac(const char *str, uint8_t addr[6]);
|
|
|
|
#define kIBootHiddenEnvDomain "com.apple.System."
|
|
|
|
#if DEBUG_BUILD
|
|
static bool nvram_whitelist_override = false;
|
|
#else
|
|
static const bool nvram_whitelist_override = false;
|
|
#endif
|
|
|
|
static bool hide_key(const char *str)
|
|
{
|
|
if (0 == strncmp(str, kIBootHiddenEnvDomain, sizeof(kIBootHiddenEnvDomain)-1))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
static struct env_var_entry *find_var_entry(const char *name)
|
|
{
|
|
struct env_var_entry *entry;
|
|
|
|
list_for_every_entry(&env_list, entry, struct env_var_entry, node) {
|
|
if (!strcmp(entry->name, name))
|
|
return entry;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const struct env_var *env_get_etc(const char *name)
|
|
{
|
|
struct env_var_entry *entry;
|
|
|
|
entry = find_var_entry(name);
|
|
if (!entry)
|
|
return NULL;
|
|
|
|
if ((entry->var.flags & ENV_SHADOW) && !nvram_whitelist_override) {
|
|
dprintf(DEBUG_CRITICAL, "\x1b[1;5;31mBlocked read\x1b[m from shadowed variable \"%s\"\n", name);
|
|
dprintf(DEBUG_CRITICAL, "Disable NVRAM whitelist with DEBUG iBoot command envprot off\n");
|
|
return NULL;
|
|
}
|
|
|
|
return &entry->var;
|
|
}
|
|
|
|
const char *env_get(const char *name)
|
|
{
|
|
const struct env_var *var;
|
|
|
|
var = env_get_etc(name);
|
|
if (!var)
|
|
return NULL;
|
|
|
|
return var->str;
|
|
}
|
|
|
|
size_t env_get_uint(const char *name, size_t default_val)
|
|
{
|
|
const struct env_var *var;
|
|
|
|
var = env_get_etc(name);
|
|
if (!var)
|
|
return default_val;
|
|
|
|
return var->u;
|
|
}
|
|
|
|
bool env_get_bool(const char *name, bool default_val)
|
|
{
|
|
const struct env_var *var;
|
|
|
|
var = env_get_etc(name);
|
|
if (NULL == var)
|
|
return default_val;
|
|
|
|
if (!strcmp(var->str, "true"))
|
|
return true;
|
|
if (0 != var->u)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static int str2ip(const char *str, uint32_t *ip)
|
|
{
|
|
char numstr[16];
|
|
int num;
|
|
int field = 0;
|
|
const char *ptr;
|
|
|
|
if (str == NULL)
|
|
return -1;
|
|
|
|
*ip = 0;
|
|
|
|
ptr = str;
|
|
for (field = 0; field < 4; field++) {
|
|
char *dot = strchr(ptr, '.');
|
|
|
|
if (dot) {
|
|
strlcpy(numstr, ptr, __min((unsigned)(dot - ptr + 1), sizeof(numstr)));
|
|
} else {
|
|
strlcpy(numstr, ptr, sizeof(numstr));
|
|
}
|
|
|
|
// printf("numstr '%s'\n", numstr);
|
|
|
|
num = strtol(numstr, NULL, 0);
|
|
if (num < 0 || num > 255) {
|
|
return -1;
|
|
}
|
|
*ip |= (num << (8 * field));
|
|
|
|
if (dot)
|
|
ptr = dot + 1;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int env_get_ipaddr(const char *name, uint32_t *ip)
|
|
{
|
|
const char *env;
|
|
|
|
env = env_get(name);
|
|
if (!env)
|
|
return -1;
|
|
|
|
if (str2ip(env, ip) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int str2mac(const char *str, uint8_t addr[6])
|
|
{
|
|
char numstr[16];
|
|
int num;
|
|
int field = 0;
|
|
const char *ptr;
|
|
char *endptr;
|
|
|
|
if (str == NULL)
|
|
return -1;
|
|
|
|
addr[0] = addr[1] = addr[2] = addr[3] = addr[4] = addr[5] = 0;
|
|
|
|
ptr = str;
|
|
for (field = 0; field < 6; field++) {
|
|
char *sep = strchr(ptr, ':');
|
|
|
|
if (sep) {
|
|
strlcpy(numstr, ptr, __min((unsigned)(sep - ptr + 1), sizeof(numstr)));
|
|
} else {
|
|
strlcpy(numstr, ptr, sizeof(numstr));
|
|
}
|
|
|
|
// printf("numstr '%s'\n", numstr);
|
|
|
|
num = strtol(numstr, &endptr, 16);
|
|
if (endptr == numstr) {
|
|
dprintf(DEBUG_INFO, "malformed MAC address %s\n", str);
|
|
return -1;
|
|
}
|
|
if (num < 0 || num > 255) {
|
|
dprintf(DEBUG_INFO, "makformed MAC address %s\n", str);
|
|
return -1;
|
|
}
|
|
addr[field] = num;
|
|
|
|
if (sep)
|
|
ptr = sep + 1;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int env_get_ethaddr(const char *name, uint8_t ethaddr[6])
|
|
{
|
|
const char *env;
|
|
|
|
env = env_get(name);
|
|
if (!env)
|
|
return -1;
|
|
|
|
if (str2mac(env, ethaddr) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int env_set(const char *name, const char *val, uint32_t flags)
|
|
{
|
|
struct env_var_entry *entry;
|
|
|
|
if (flags & ENV_PERSISTENT) {
|
|
if (env_blacklist_nvram(name))
|
|
flags |= ENV_SHADOW;
|
|
}
|
|
|
|
/* see if it already exists */
|
|
entry = find_var_entry(name);
|
|
if (entry) {
|
|
if ((flags & ENV_SHADOW) && !(entry->var.flags & ENV_SHADOW) && !nvram_whitelist_override) {
|
|
dprintf(DEBUG_CRITICAL, "\x1b[1;5;31mBlocked shadowed write\x1b[m to variable \"%s\"\n", name);
|
|
dprintf(DEBUG_CRITICAL, "Disable NVRAM whitelist with DEBUG iBoot command envprot off\n");
|
|
// If the variable exists, is not on the whitelist for NVRAM variables,
|
|
// and the value was generated internally (probably from the default environment),
|
|
// refuse to modify the existing value.
|
|
// This will result in the variable getting removed from nvram on saveenv, but
|
|
// protects us from people removing things from the default environment.
|
|
return -1;
|
|
} else {
|
|
// If the variable exists and either is on the whitelist or was not
|
|
// generated internally, delete the old value
|
|
env_unset(name);
|
|
}
|
|
}
|
|
|
|
entry = malloc(sizeof(struct env_var_entry));
|
|
|
|
strlcpy(entry->name, name, sizeof(entry->name));
|
|
|
|
entry->var.str = strdup(val);
|
|
if (entry->var.str == NULL) {
|
|
free(entry);
|
|
return -1;
|
|
}
|
|
|
|
entry->var.u = strtoul(entry->var.str, NULL, 0);
|
|
|
|
/* save the flags */
|
|
entry->var.flags = flags;
|
|
|
|
list_add_tail(&env_list, &entry->node);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int env_set_uint(const char *name, size_t val, uint32_t flags)
|
|
{
|
|
char numbuf[(sizeof(size_t) * 2) + 3]; // plus '3' to store "0x" and null terminator
|
|
|
|
snprintf(numbuf, sizeof(numbuf), "0x%lx", val);
|
|
|
|
return env_set(name, numbuf, flags);
|
|
}
|
|
|
|
int env_unset(const char *name)
|
|
{
|
|
struct env_var_entry *entry;
|
|
|
|
entry = find_var_entry(name);
|
|
if (!entry)
|
|
return 0;
|
|
|
|
list_delete(&entry->node);
|
|
|
|
free(entry->var.str);
|
|
free(entry);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* used by the nvram code to write the environment out to nvram.
|
|
* serialize all of the persistent environment variables with a simple
|
|
* <name>=<value>NULL...
|
|
* strategy.
|
|
*/
|
|
size_t env_serialize(uint8_t *buf, size_t buf_len)
|
|
{
|
|
size_t offset;
|
|
struct env_var_entry *entry;
|
|
|
|
offset = 0;
|
|
list_for_every_entry(&env_list, entry, struct env_var_entry, node) {
|
|
if (entry->var.flags & ENV_PERSISTENT) {
|
|
/* print one var into the buffer, avoiding overflow */
|
|
offset += snprintf((void *)(buf + offset), buf_len - offset, "%s=%s", entry->name, entry->var.str) + 1;
|
|
|
|
/* effectively require there to always be at least one free byte */
|
|
if (offset >= buf_len)
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* opposite of the above */
|
|
int env_unserialize(const uint8_t *buf, size_t buf_len)
|
|
{
|
|
size_t offset;
|
|
|
|
#if 0
|
|
dprintf(DEBUG_SPEW, "serialized environment vars:\n");
|
|
dhexdump(DEBUG_SPEW, buf, buf_len);
|
|
#endif
|
|
|
|
offset = 0;
|
|
while (offset < buf_len) {
|
|
char envname[64];
|
|
char envval[256];
|
|
uint32_t var_start, var_end;
|
|
uint32_t val_start, val_end;
|
|
|
|
if (buf[offset] == 0) {
|
|
offset++;
|
|
continue;
|
|
}
|
|
|
|
/* find the start and end of the variable name */
|
|
var_start = offset;
|
|
for (var_end = var_start; buf[var_end] != '=' && buf[var_end] != 0; var_end++)
|
|
;
|
|
if (buf[var_end] == 0) // malformed variable, no '='
|
|
break;
|
|
|
|
/* find the start and end of the value component */
|
|
val_start = var_end + 1; /* skip the '=' */
|
|
for (val_end = val_start; buf[val_end] != 0; val_end++)
|
|
;
|
|
|
|
/* copy them out */
|
|
strlcpy(envname, (const char *)&buf[var_start], __min(var_end - var_start + 1, sizeof(envname)));
|
|
strlcpy(envval, (const char *)&buf[val_start], __min(val_end - val_start + 1, sizeof(envval)));
|
|
|
|
dprintf(DEBUG_SPEW, "'%s' = '%s'\n", envname, envval);
|
|
|
|
/* load the environment */
|
|
if (strlen(envname) > 0 && strlen(envval) > 0) {
|
|
uint32_t flags = ENV_PERSISTENT;
|
|
if (env_blacklist_nvram(envname))
|
|
flags |= ENV_SHADOW;
|
|
env_set(envname, envval, flags);
|
|
}
|
|
|
|
offset = val_end + 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* for unit testing testing purposes only, remove every environment
|
|
* variable
|
|
*/
|
|
void env_reset(void)
|
|
{
|
|
struct env_var_entry *entry;
|
|
struct env_var_entry *temp;
|
|
|
|
list_for_every_entry_safe(&env_list, entry, temp, struct env_var_entry, node) {
|
|
env_unset(entry->name);
|
|
}
|
|
}
|
|
|
|
|
|
/* command prompt routines */
|
|
static void dump_var(const struct env_var_entry *entry)
|
|
{
|
|
int i, binary = 0;
|
|
uint32_t flags;
|
|
|
|
/* filter out non-ascii strings in variable values */
|
|
for (i = 0; i < ENV_VAR_ENTRY_NAME_LEN; i++) {
|
|
if (entry->var.str[i] == 0)
|
|
break;
|
|
if (entry->var.str[i] < 0x20 || entry->var.str[i] > 0x7e)
|
|
binary = 1;
|
|
}
|
|
|
|
flags = entry->var.flags;
|
|
printf("%c %s = %s%s%s\n",
|
|
(flags & ENV_SHADOW) ? 'S' : ((flags & ENV_PERSISTENT) ? 'P' : ' '),
|
|
entry->name,
|
|
binary ? "" : "\"",
|
|
binary ? "<DATA>" : entry->var.str,
|
|
binary ? "" : "\"");
|
|
}
|
|
|
|
int do_printenv(int argc, struct cmd_arg *args)
|
|
{
|
|
struct env_var_entry *entry;
|
|
|
|
if (argc >= 2) {
|
|
/* display a single variable */
|
|
entry = find_var_entry(args[1].str);
|
|
if (entry) {
|
|
if (!hide_key(entry->name))
|
|
dump_var(entry);
|
|
} else {
|
|
printf("variable %s not set\n", args[1].str);
|
|
}
|
|
} else {
|
|
/* display all variables */
|
|
list_for_every_entry(&env_list, entry, struct env_var_entry, node) {
|
|
if (hide_key(entry->name)) continue;
|
|
dump_var(entry);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_getenv(int argc, struct cmd_arg *args)
|
|
{
|
|
struct env_var_entry *entry;
|
|
if(argc != 2)
|
|
{
|
|
#if !RELEASE_BUILD
|
|
printf("%s <var>\n", args[0].str);
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
entry = find_var_entry(args[1].str);
|
|
if(!entry)
|
|
return -1;
|
|
if(hide_key(entry->name))
|
|
return -1;
|
|
if(env_blacklist(entry->name, false))
|
|
return -1;
|
|
return env_set("cmd-results", entry->var.str, 0);
|
|
}
|
|
|
|
int do_setenv(int argc, struct cmd_arg *args)
|
|
{
|
|
if (argc < 2) {
|
|
#if !RELEASE_BUILD
|
|
printf("not enough arguments.\n");
|
|
printf("%s <var> [<value>] ...\n", args[0].str);
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
if (hide_key(args[1].str)) return -1;
|
|
|
|
if (env_blacklist(args[1].str, true)) return -1;
|
|
|
|
if (argc < 3) {
|
|
env_unset(args[1].str);
|
|
} else {
|
|
size_t buf_size = 512;
|
|
char *envbuf = malloc(buf_size);
|
|
int i;
|
|
|
|
/* reassemble a string from all the command line args */
|
|
envbuf[0] = 0;
|
|
for (i = 2; i < argc; i++) {
|
|
strlcat(envbuf, args[i].str, buf_size);
|
|
if (i != (argc - 1))
|
|
strlcat(envbuf, " ", buf_size);
|
|
}
|
|
|
|
env_set(args[1].str, envbuf, ENV_PERSISTENT);
|
|
|
|
free(envbuf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_clearenv(int argc, struct cmd_arg *args)
|
|
{
|
|
struct env_var_entry *entry, *pentry;
|
|
|
|
if (argc < 2) {
|
|
printf("not enough arguments.\n");
|
|
printf("Usage: It's a two step process. Run the following commands to clear\n");
|
|
printf("\t\tclearenv <var>\n");
|
|
printf("\t\tclearenv 1 \t {confirms the clear}\n");
|
|
printf("ex: clearenv auto-boot boot-args\n");
|
|
printf(" clearenv 1\n");
|
|
return -1;
|
|
}
|
|
|
|
if (args[1].u == 0) {
|
|
printf("Clear pending. Run command \"clearenv 1\" to confirm\n");
|
|
return -1;
|
|
}
|
|
|
|
/* clear all persistent variables */
|
|
while (1) {
|
|
pentry = 0;
|
|
list_for_every_entry(&env_list, entry, struct env_var_entry, node) {
|
|
if (hide_key(entry->name)) continue;
|
|
if (entry->var.flags & ENV_PERSISTENT) {
|
|
pentry = entry;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pentry == 0) break;
|
|
|
|
env_unset(pentry->name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if DEBUG_BUILD
|
|
int
|
|
do_envprot(int argc, struct cmd_arg *args)
|
|
{
|
|
int result = 0;
|
|
|
|
if (argc != 2) {
|
|
result = -1;
|
|
} else {
|
|
if (strcmp(args[1].str, "off") == 0) {
|
|
nvram_whitelist_override = true;
|
|
printf("Disabled NVRAM whitelist\n");
|
|
} else if (strcmp(args[1].str, "on") == 0) {
|
|
nvram_whitelist_override = false;
|
|
printf("Enabled NVRAM whitelist\n");
|
|
} else {
|
|
result = -1;
|
|
}
|
|
}
|
|
|
|
if (result < 0)
|
|
printf("Usage: envprot {on | off}\n");
|
|
|
|
return result;
|
|
}
|
|
#endif
|