/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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 = G_MAXUINT8; 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_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 for existing user, and if not then remember the first spare slot */ for (guint8 i = 2; i < 0xFF; i++) { g_autofree gchar *username = fu_ipmi_device_get_user_password(device, i, NULL); if (username == NULL && user_id == G_MAXUINT8) { g_debug("KCS slot %u free", i); user_id = i; continue; } if (g_strcmp0(username, "fwupd") == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "fwupd user already exists in KCS slot %u", (guint)i); return FALSE; } } if (user_id == G_MAXUINT8) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "all KCS slots full, cannot create user"); 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); }