mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-30 14:51:04 +00:00

This allows the Redfish plugin to "just work" when there is no username or password in the SMBIOS data. Using KCS we can create an admin account from the host OS and then automatically enumerate devices.
366 lines
12 KiB
C
366 lines
12 KiB
C
/*
|
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#ifdef HAVE_LINUX_IPMI_H
|
|
#include "fu-ipmi-device.h"
|
|
#endif
|
|
|
|
#include "fu-redfish-backend.h"
|
|
#include "fu-redfish-common.h"
|
|
#include "fu-redfish-network.h"
|
|
#include "fu-redfish-smbios.h"
|
|
|
|
struct FuPluginData {
|
|
FuRedfishBackend *backend;
|
|
};
|
|
|
|
gboolean
|
|
fu_plugin_coldplug(FuPlugin *plugin, GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data(plugin);
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* get the list of devices */
|
|
if (!fu_backend_coldplug(FU_BACKEND(data->backend), &error_local)) {
|
|
if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED))
|
|
fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_AUTH_REQUIRED);
|
|
g_propagate_error(error, g_steal_pointer(&error_local));
|
|
return FALSE;
|
|
}
|
|
devices = fu_backend_get_devices(FU_BACKEND(data->backend));
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
FuDevice *device = g_ptr_array_index(devices, i);
|
|
if (fu_plugin_has_custom_flag(plugin, "reset-required"))
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
|
|
fu_plugin_device_add(plugin, device);
|
|
}
|
|
|
|
/* this is no longer relevant */
|
|
if (devices->len > 0) {
|
|
fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "bios");
|
|
fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "uefi_capsule");
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_redfish_plugin_discover_uefi_credentials(FuPlugin *plugin, GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data(plugin);
|
|
gsize bufsz = 0;
|
|
guint32 indications = 0x0;
|
|
g_autofree gchar *userpass_safe = NULL;
|
|
g_autofree guint8 *buf = NULL;
|
|
g_auto(GStrv) split = NULL;
|
|
g_autoptr(GBytes) userpass = NULL;
|
|
|
|
/* get the uint32 specifying if there are EFI variables set */
|
|
if (!fu_efivar_get_data(REDFISH_EFI_INFORMATION_GUID,
|
|
REDFISH_EFI_INFORMATION_INDICATIONS,
|
|
&buf,
|
|
&bufsz,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint32_safe(buf, bufsz, 0x0, &indications, G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
if ((indications & REDFISH_EFI_INDICATIONS_OS_CREDENTIALS) == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"no indications for OS credentials");
|
|
return FALSE;
|
|
}
|
|
|
|
/* read the correct EFI var for runtime */
|
|
userpass = fu_efivar_get_data_bytes(REDFISH_EFI_INFORMATION_GUID,
|
|
REDFISH_EFI_INFORMATION_OS_CREDENTIALS,
|
|
NULL,
|
|
error);
|
|
if (userpass == NULL)
|
|
return FALSE;
|
|
|
|
/* it might not be NUL terminated */
|
|
userpass_safe = g_strndup(g_bytes_get_data(userpass, NULL), g_bytes_get_size(userpass));
|
|
split = g_strsplit(userpass_safe, ":", -1);
|
|
if (g_strv_length(split) != 2) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"invalid format for username:password, got '%s'",
|
|
userpass_safe);
|
|
return FALSE;
|
|
}
|
|
fu_redfish_backend_set_username(data->backend, split[0]);
|
|
fu_redfish_backend_set_password(data->backend, split[1]);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_redfish_plugin_discover_smbios_table(FuPlugin *plugin, GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data(plugin);
|
|
FuContext *ctx = fu_plugin_get_context(plugin);
|
|
const gchar *smbios_data_fn;
|
|
g_autofree gchar *hostname = NULL;
|
|
g_autoptr(FuRedfishNetworkDevice) device = NULL;
|
|
g_autoptr(FuRedfishSmbios) redfish_smbios = fu_redfish_smbios_new();
|
|
g_autoptr(GBytes) smbios_data = NULL;
|
|
|
|
/* is optional if not in self tests */
|
|
smbios_data_fn = g_getenv("FWUPD_REDFISH_SMBIOS_DATA");
|
|
if (smbios_data_fn != NULL) {
|
|
smbios_data = fu_common_get_contents_bytes(smbios_data_fn, error);
|
|
if (smbios_data == NULL)
|
|
return FALSE;
|
|
} else {
|
|
smbios_data = fu_context_get_smbios_data(ctx, REDFISH_SMBIOS_TABLE_TYPE);
|
|
if (smbios_data == NULL)
|
|
return TRUE;
|
|
}
|
|
if (!fu_firmware_parse(FU_FIRMWARE(redfish_smbios),
|
|
smbios_data,
|
|
FWUPD_INSTALL_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to parse SMBIOS table entry type 42: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* get IP, falling back to hostname, then MAC, then VID:PID */
|
|
hostname = g_strdup(fu_redfish_smbios_get_ip_addr(redfish_smbios));
|
|
if (hostname == NULL)
|
|
hostname = g_strdup(fu_redfish_smbios_get_hostname(redfish_smbios));
|
|
if (device == NULL) {
|
|
const gchar *mac_addr = fu_redfish_smbios_get_mac_addr(redfish_smbios);
|
|
if (mac_addr != NULL) {
|
|
g_autoptr(GError) error_network = NULL;
|
|
device = fu_redfish_network_device_for_mac_addr(mac_addr, &error_network);
|
|
if (device == NULL)
|
|
g_debug("failed to get device: %s", error_network->message);
|
|
}
|
|
}
|
|
if (device == NULL) {
|
|
guint16 vid = fu_redfish_smbios_get_vid(redfish_smbios);
|
|
guint16 pid = fu_redfish_smbios_get_pid(redfish_smbios);
|
|
if (vid != 0x0 && pid != 0x0) {
|
|
g_autoptr(GError) error_network = NULL;
|
|
device = fu_redfish_network_device_for_vid_pid(vid, pid, &error_network);
|
|
if (device == NULL)
|
|
g_debug("failed to get device: %s", error_network->message);
|
|
}
|
|
}
|
|
|
|
/* autoconnect device if required */
|
|
if (device != NULL) {
|
|
FuRedfishNetworkDeviceState state = FU_REDFISH_NETWORK_DEVICE_STATE_UNKNOWN;
|
|
if (!fu_redfish_network_device_get_state(device, &state, error))
|
|
return FALSE;
|
|
if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) {
|
|
g_debug("device state is now %u", state);
|
|
}
|
|
if (state == FU_REDFISH_NETWORK_DEVICE_STATE_DISCONNECTED) {
|
|
if (!fu_redfish_network_device_connect(device, error))
|
|
return FALSE;
|
|
}
|
|
if (hostname == NULL) {
|
|
hostname = fu_redfish_network_device_get_address(device, error);
|
|
if (hostname == NULL)
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (hostname == NULL) {
|
|
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no hostname");
|
|
return FALSE;
|
|
}
|
|
fu_redfish_backend_set_hostname(data->backend, hostname);
|
|
fu_redfish_backend_set_port(data->backend, fu_redfish_smbios_get_port(redfish_smbios));
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef HAVE_LINUX_IPMI_H
|
|
static gchar *
|
|
fu_common_generate_password(guint length)
|
|
{
|
|
GString *str = g_string_sized_new(length);
|
|
|
|
/* get a random password string */
|
|
while (str->len < length) {
|
|
gchar tmp = (gchar)g_random_int_range(0x0, 0xff);
|
|
if (g_ascii_isalnum(tmp))
|
|
g_string_append_c(str, tmp);
|
|
}
|
|
return g_string_free(str, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
fu_redfish_plugin_ipmi_create_user(FuPlugin *plugin, GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data(plugin);
|
|
const gchar *username_fwupd = "fwupd";
|
|
guint8 user_id = 0x04;
|
|
g_autofree gchar *password_new = fu_common_generate_password(15);
|
|
g_autofree gchar *password_tmp = fu_common_generate_password(15);
|
|
g_autofree gchar *uri = NULL;
|
|
g_autofree gchar *username = NULL;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_autoptr(FuIpmiDevice) device = fu_ipmi_device_new(fu_plugin_get_context(plugin));
|
|
g_autoptr(FuRedfishRequest) request = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(JsonBuilder) builder = json_builder_new();
|
|
|
|
/* create device */
|
|
locker = fu_device_locker_new(device, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
|
|
/* check the slot is clear */
|
|
username = fu_ipmi_device_get_user_password(device, 0x04, NULL);
|
|
if (username != NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot create fwupd user with account %s already existing",
|
|
username);
|
|
return FALSE;
|
|
}
|
|
|
|
/* create a user with appropriate permissions */
|
|
if (!fu_ipmi_device_set_user_name(device, user_id, username_fwupd, error))
|
|
return FALSE;
|
|
if (!fu_ipmi_device_set_user_enable(device, user_id, TRUE, error))
|
|
return FALSE;
|
|
if (!fu_ipmi_device_set_user_priv(device, user_id, 0x4, 1, error))
|
|
return FALSE;
|
|
if (!fu_ipmi_device_set_user_password(device, user_id, password_tmp, error))
|
|
return FALSE;
|
|
fu_redfish_backend_set_username(data->backend, username_fwupd);
|
|
fu_redfish_backend_set_password(data->backend, password_tmp);
|
|
|
|
/* now use Redfish to change the temporary password to the actual password */
|
|
request = fu_redfish_backend_request_new(data->backend);
|
|
uri = g_strdup_printf("/redfish/v1/AccountService/Accounts/%u", (guint)user_id - 1);
|
|
json_builder_begin_object(builder);
|
|
json_builder_set_member_name(builder, "Password");
|
|
json_builder_add_string_value(builder, password_new);
|
|
json_builder_end_object(builder);
|
|
if (!fu_redfish_request_patch(request,
|
|
uri,
|
|
builder,
|
|
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
|
error))
|
|
return FALSE;
|
|
fu_redfish_backend_set_password(data->backend, password_new);
|
|
|
|
/* success */
|
|
if (!fu_plugin_set_config_value(plugin, "Username", username_fwupd, error))
|
|
return FALSE;
|
|
if (!fu_plugin_set_config_value(plugin, "Password", password_new, error))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
gboolean
|
|
fu_plugin_startup(FuPlugin *plugin, GError **error)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data(plugin);
|
|
g_autofree gchar *ca_check_str = NULL;
|
|
g_autofree gchar *password = NULL;
|
|
g_autofree gchar *redfish_uri = NULL;
|
|
g_autofree gchar *username = NULL;
|
|
g_autoptr(GError) error_uefi = NULL;
|
|
|
|
/* optional */
|
|
if (!fu_redfish_plugin_discover_smbios_table(plugin, error))
|
|
return FALSE;
|
|
if (!fu_redfish_plugin_discover_uefi_credentials(plugin, &error_uefi)) {
|
|
g_debug("failed to get username and password automatically: %s",
|
|
error_uefi->message);
|
|
}
|
|
|
|
/* override with the conf file */
|
|
redfish_uri = fu_plugin_get_config_value(plugin, "Uri");
|
|
if (redfish_uri != NULL) {
|
|
const gchar *ip_str = NULL;
|
|
g_auto(GStrv) split = NULL;
|
|
guint64 port;
|
|
|
|
if (g_str_has_prefix(redfish_uri, "https://")) {
|
|
fu_redfish_backend_set_https(data->backend, TRUE);
|
|
ip_str = redfish_uri + strlen("https://");
|
|
} else if (g_str_has_prefix(redfish_uri, "http://")) {
|
|
fu_redfish_backend_set_https(data->backend, FALSE);
|
|
ip_str = redfish_uri + strlen("http://");
|
|
} else {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"in valid scheme");
|
|
return FALSE;
|
|
}
|
|
|
|
split = g_strsplit(ip_str, ":", 2);
|
|
fu_redfish_backend_set_hostname(data->backend, split[0]);
|
|
port = g_ascii_strtoull(split[1], NULL, 10);
|
|
if (port == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"no port specified");
|
|
return FALSE;
|
|
}
|
|
fu_redfish_backend_set_port(data->backend, port);
|
|
}
|
|
username = fu_plugin_get_config_value(plugin, "Username");
|
|
if (username != NULL)
|
|
fu_redfish_backend_set_username(data->backend, username);
|
|
password = fu_plugin_get_config_value(plugin, "Password");
|
|
if (password != NULL)
|
|
fu_redfish_backend_set_password(data->backend, password);
|
|
ca_check_str = fu_plugin_get_config_value(plugin, "CACheck");
|
|
if (ca_check_str != NULL) {
|
|
gboolean ca_check = fu_plugin_get_config_value_boolean(plugin, "CACheck");
|
|
fu_redfish_backend_set_cacheck(data->backend, ca_check);
|
|
}
|
|
if (fu_plugin_has_custom_flag(plugin, "wildcard-targets"))
|
|
fu_redfish_backend_set_wildcard_targets(data->backend, TRUE);
|
|
|
|
#ifdef HAVE_LINUX_IPMI_H
|
|
/* we got neither a type 42 entry or config value, lets try IPMI */
|
|
if (fu_redfish_backend_get_username(data->backend) == NULL) {
|
|
if (!fu_plugin_get_config_value_boolean(plugin, "IpmiDisableCreateUser")) {
|
|
g_debug("attempting to create user using IPMI");
|
|
if (!fu_redfish_plugin_ipmi_create_user(plugin, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return fu_backend_setup(FU_BACKEND(data->backend), error);
|
|
}
|
|
|
|
void
|
|
fu_plugin_init(FuPlugin *plugin)
|
|
{
|
|
FuContext *ctx = fu_plugin_get_context(plugin);
|
|
FuPluginData *data = fu_plugin_alloc_data(plugin, sizeof(FuPluginData));
|
|
data->backend = fu_redfish_backend_new(ctx);
|
|
fu_plugin_set_build_hash(plugin, FU_BUILD_HASH);
|
|
fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_REDFISH_SMBIOS);
|
|
}
|
|
|
|
void
|
|
fu_plugin_destroy(FuPlugin *plugin)
|
|
{
|
|
FuPluginData *data = fu_plugin_get_data(plugin);
|
|
g_object_unref(data->backend);
|
|
}
|