diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index d9555ee4c..026149cb7 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -352,6 +352,7 @@ done %if 0%{?have_msr} /usr/lib/modules-load.d/fwupd-msr.conf %endif +/usr/lib/modules-load.d/fwupd-redfish.conf %{_datadir}/dbus-1/system.d/org.freedesktop.fwupd.conf %{_datadir}/bash-completion/completions/fwupdmgr %{_datadir}/bash-completion/completions/fwupdtool diff --git a/meson.build b/meson.build index 2ac22cd44..1376dc706 100644 --- a/meson.build +++ b/meson.build @@ -360,6 +360,18 @@ endif if cc.has_header_symbol('locale.h', 'LC_MESSAGES') conf.set('HAVE_LC_MESSAGES', '1') endif +if cc.has_header('linux/ipmi.h') + have_linux_ipmi = true + conf.set('HAVE_LINUX_IPMI_H', '1') +else + have_linux_ipmi = false +endif +if cc.has_header_symbol('linux/ipmi_msgdefs.h', 'IPMI_DEVICE_IN_FW_UPDATE_ERR') + conf.set('HAVE_IPMI_DEVICE_IN_FW_UPDATE_ERR', '1') +endif +if cc.has_header_symbol('linux/ipmi_msgdefs.h', 'IPMI_DEVICE_IN_INIT_ERR') + conf.set('HAVE_IPMI_DEVICE_IN_INIT_ERR', '1') +endif if cc.has_header_symbol('fcntl.h', 'F_WRLCK') conf.set('HAVE_WRLCK', '1') endif diff --git a/plugins/redfish/fu-ipmi-device.c b/plugins/redfish/fu-ipmi-device.c new file mode 100644 index 000000000..172c5b45b --- /dev/null +++ b/plugins/redfish/fu-ipmi-device.c @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fu-ipmi-device.h" + +#define FU_IPMI_DEVICE_TIMEOUT 1500 /* ms */ + +/* not defined in linux/ipmi_msgdefs.h */ +#define IPMI_SET_USER_ACCESS 0x43 +#define IPMI_SET_USER_NAME 0x45 +#define IPMI_GET_USER_NAME 0x46 +#define IPMI_SET_USER_PASSWORD 0x47 + +#define IPMI_PASSWORD_DISABLE_USER 0x00 +#define IPMI_PASSWORD_ENABLE_USER 0x01 +#define IPMI_PASSWORD_SET_PASSWORD 0x02 +#define IPMI_PASSWORD_TEST_PASSWORD 0x03 + +struct _FuIpmiDevice { + FuUdevDevice parent_instance; + glong seq; + guint8 device_id; + guint8 device_rev; + guint8 version_ipmi; +}; + +G_DEFINE_TYPE(FuIpmiDevice, fu_ipmi_device, FU_TYPE_UDEV_DEVICE) + +static void +fu_ipmi_device_to_string(FuDevice *device, guint idt, GString *str) +{ + FuIpmiDevice *self = FU_IPMI_DEVICE(device); + fu_common_string_append_kx(str, idt, "DeviceId", self->device_id); + fu_common_string_append_kx(str, idt, "DeviceRev", self->device_rev); + fu_common_string_append_kx(str, idt, "VersionIpmi", self->version_ipmi); +} + +static gboolean +fu_ipmi_device_send(FuIpmiDevice *self, + guint8 netfn, + guint8 cmd, + const guint8 *buf, + gsize bufsz, + GError **error) +{ + g_autofree guint8 *buf2 = fu_memdup_safe(buf, bufsz, NULL); + struct ipmi_system_interface_addr addr = {.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE, + .channel = IPMI_BMC_CHANNEL}; + struct ipmi_req req = { + .addr = (guint8 *)&addr, + .addr_len = sizeof(addr), + .msgid = self->seq++, + .msg.data = buf2, + .msg.data_len = (guint16)bufsz, + .msg.netfn = netfn, + .msg.cmd = cmd, + }; + if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) + fu_common_dump_raw(G_LOG_DOMAIN, "ipmi-send", buf2, bufsz); + return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), + IPMICTL_SEND_COMMAND, + (guint8 *)&req, + NULL, + error); +} + +static gboolean +fu_ipmi_device_recv(FuIpmiDevice *self, + guint8 *netfn, + guint8 *cmd, + glong *seq, + guint8 *buf, + gsize bufsz, + gsize *len, /* optional, out */ + GError **error) +{ + struct ipmi_addr addr = {0}; + struct ipmi_recv recv = { + .addr = (guint8 *)&addr, + .addr_len = sizeof(addr), + .msg.data = buf, + .msg.data_len = bufsz, + }; + if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), + IPMICTL_RECEIVE_MSG_TRUNC, + (guint8 *)&recv, + NULL, + error)) + return FALSE; + if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) + fu_common_dump_raw(G_LOG_DOMAIN, "ipmi-recv", buf, *len); + if (netfn != NULL) + *netfn = recv.msg.netfn; + if (cmd != NULL) + *cmd = recv.msg.cmd; + if (seq != NULL) + *seq = recv.msgid; + if (len != NULL) + *len = (gsize)recv.msg.data_len; + return TRUE; +} + +static gboolean +fu_ipmi_device_lock(GObject *device, GError **error) +{ + FuIpmiDevice *self = FU_IPMI_DEVICE(device); + struct flock lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET}; + if (fcntl(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), F_SETLKW, &lock) == -1) { + g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "error locking IPMI device: %m"); + return FALSE; + } + return TRUE; +} + +static gboolean +fu_ipmi_device_unlock(GObject *device, GError **error) +{ + FuIpmiDevice *self = FU_IPMI_DEVICE(device); + struct flock lock = {.l_type = F_UNLCK}; + if (fcntl(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), F_SETLKW, &lock) == -1) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "error unlocking IPMI device: %m"); + return FALSE; + } + return TRUE; +} + +static const gchar * +fu_ipmi_device_errcode_to_string(guint8 errcode) +{ + if (errcode == IPMI_CC_NO_ERROR) + return "no-error"; + if (errcode == IPMI_NODE_BUSY_ERR) + return "node-busy"; + if (errcode == IPMI_INVALID_COMMAND_ERR) + return "invalid-command"; + if (errcode == IPMI_TIMEOUT_ERR) + return "timeout"; + if (errcode == IPMI_ERR_MSG_TRUNCATED) + return "msg-truncated"; + if (errcode == IPMI_REQ_LEN_INVALID_ERR) + return "req-len-invalid"; + if (errcode == IPMI_REQ_LEN_EXCEEDED_ERR) + return "req-len-exceeded"; +#ifdef HAVE_IPMI_DEVICE_IN_FW_UPDATE_ERR + if (errcode == IPMI_DEVICE_IN_FW_UPDATE_ERR) + return "device-in-fw-update"; +#endif +#ifdef HAVE_IPMI_DEVICE_IN_INIT_ERR + if (errcode == IPMI_DEVICE_IN_INIT_ERR) + return "device-in-init"; +#endif + if (errcode == IPMI_NOT_IN_MY_STATE_ERR) + return "not-in-my-state"; + if (errcode == IPMI_LOST_ARBITRATION_ERR) + return "lost-arbitration"; + if (errcode == IPMI_BUS_ERR) + return "bus-error"; + if (errcode == IPMI_NAK_ON_WRITE_ERR) + return "nak-on-write"; + if (errcode == IPMI_ERR_UNSPECIFIED) + return "unspecified"; + return "unspecified"; +} + +static gboolean +fu_ipmi_device_transaction(FuIpmiDevice *self, + guint8 netfn, + guint8 cmd, + const guint8 *req_buf, + gsize req_bufsz, + guint8 *resp_buf, /* optional */ + gsize resp_bufsz, + gsize *resp_len, /* optional, out */ + gint timeout_ms, + GError **error) +{ + GPollFD pollfds[1]; + gsize resp_buf2sz = resp_bufsz + 1; + gsize resp_len2 = 0; + g_autoptr(GTimer) timer = g_timer_new(); + g_autoptr(FuDeviceLocker) lock = NULL; + g_autofree guint8 *resp_buf2 = g_malloc0(resp_buf2sz); + + lock = fu_device_locker_new_full(self, fu_ipmi_device_lock, fu_ipmi_device_unlock, error); + if (lock == NULL) + return FALSE; + + if (!fu_ipmi_device_send(self, netfn, cmd, req_buf, req_bufsz, error)) + return FALSE; + + pollfds[0].fd = fu_udev_device_get_fd(FU_UDEV_DEVICE(self)); + pollfds[0].events = POLLIN; + + for (;;) { + guint8 resp_netfn = 0; + guint8 resp_cmd = 0; + glong seq = 0; + gint rc; + + rc = g_poll(pollfds, 1, timeout_ms - (g_timer_elapsed(timer, NULL) * 1000.f)); + if (rc < 0) { + g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "poll() error %m"); + return FALSE; + } + if (rc == 0) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "timeout waiting for response " + "(netfn %d, cmd %d)", + netfn, + cmd); + return FALSE; + } + + if (!(pollfds[0].revents & POLLIN)) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "unexpected status"); + return FALSE; + } + + if (!fu_ipmi_device_recv(self, + &resp_netfn, + &resp_cmd, + &seq, + resp_buf2, + resp_buf2sz, + &resp_len2, + error)) + return FALSE; + + if (seq != self->seq - 1) { + g_debug("out-of-sequence reply: " + "expected %ld, got %ld", + self->seq, + seq); + if (g_timer_elapsed(timer, NULL) * 1000.f >= timeout_ms) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "timed out"); + return FALSE; + } + } else { + if (resp_buf2[0] != IPMI_CC_NO_ERROR) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "CC error: %s [%02x]", + fu_ipmi_device_errcode_to_string(resp_buf2[0]), + resp_buf2[0]); + return FALSE; + } + if (resp_buf != NULL) { + if (!fu_memcpy_safe(resp_buf, + resp_bufsz, + 0x0, /* dst */ + resp_buf2, + resp_buf2sz, + 0x01, /* src */ + resp_bufsz, + error)) + return FALSE; + } + if (resp_len != NULL) + *resp_len = resp_len2 - 1; + if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) { + g_debug("IPMI netfn: %02x->%02x, cmd: %02x->%02x", + netfn, + resp_netfn, + cmd, + resp_cmd); + } + break; + } + } + return TRUE; +} + +static gboolean +fu_ipmi_device_probe(FuDevice *device, GError **error) +{ + FuIpmiDevice *self = FU_IPMI_DEVICE(device); + const gchar *physical_ids[] = {"/dev/ipmi0", "/dev/ipmi/0", "/dev/ipmidev/0", NULL}; + + /* look for the IPMI device */ + for (guint i = 0; physical_ids[i] != NULL; i++) { + if (g_file_test(physical_ids[i], G_FILE_TEST_EXISTS)) { + fu_device_set_physical_id(FU_DEVICE(self), physical_ids[i]); + return TRUE; + } + } + + /* cannot continue */ + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no BMC device found"); + return FALSE; +} + +static gboolean +fu_ipmi_device_setup(FuDevice *device, GError **error) +{ + FuIpmiDevice *self = FU_IPMI_DEVICE(device); + gsize resp_len = 0; + guint8 resp[16] = {0}; + + /* get IPMI versions */ + if (!fu_ipmi_device_transaction(self, + IPMI_NETFN_APP_REQUEST, + IPMI_GET_DEVICE_ID_CMD, + NULL, + 0, + resp, + sizeof(resp), + &resp_len, + FU_IPMI_DEVICE_TIMEOUT, + error)) + return FALSE; + if (resp_len == 11 || resp_len == 15) { + guint8 bcd; + g_autoptr(GString) str = g_string_new(NULL); + + self->device_id = resp[0]; + self->device_rev = resp[1]; + bcd = resp[3] & 0x0f; + bcd += 10 * (resp[4] >> 3); + /* rev1.rev2.aux_revision */ + g_string_append_printf(str, "%u.%02u", resp[2], bcd); + if (resp_len == 15) { + g_string_append_printf(str, + ".%02x%02x%02x%02x", + resp[11], + resp[12], + resp[13], + resp[14]); + } + fu_device_set_version(device, str->str); + bcd = resp[4] & 0x0f; + bcd += 10 * (resp[4] >> 4); + self->version_ipmi = bcd; + } else { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "failed to parse DEVICE_ID_CMD response (sz: %" G_GSIZE_FORMAT ")", + resp_len); + return FALSE; + } + + /* success */ + return TRUE; +} + +gchar * +fu_ipmi_device_get_user_password(FuIpmiDevice *self, guint8 user_id, GError **error) +{ + const guint8 req[1] = {user_id}; + guint8 resp[0x10] = {0}; + gsize resp_len = 0; + + g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), NULL); + g_return_val_if_fail(user_id != 0x0, NULL); + + /* run transaction */ + if (!fu_ipmi_device_transaction(self, + IPMI_NETFN_APP_REQUEST, + IPMI_GET_USER_NAME, + req, + sizeof(req), + resp, + sizeof(resp), + &resp_len, + FU_IPMI_DEVICE_TIMEOUT, + error)) { + g_prefix_error(error, "failed to get username: "); + return NULL; + } + if (resp_len != sizeof(resp)) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "failed to retrieve username from IPMI, got 0x%x bytes", + (guint)resp_len); + return NULL; + } + + /* success */ + return fu_common_strsafe((const gchar *)resp, resp_len); +} + +gboolean +fu_ipmi_device_set_user_name(FuIpmiDevice *self, + guint8 user_id, + const gchar *username, + GError **error) +{ + guint8 req[0x11] = {user_id}; + gsize username_sz; + + g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); + g_return_val_if_fail(user_id != 0x0, FALSE); + g_return_val_if_fail(username != NULL, FALSE); + + /* copy into buffer */ + username_sz = strlen(username); + if (!fu_memcpy_safe(req, + sizeof(req), + 0x1, /* dst */ + (guint8 *)username, + username_sz, + 0x0, /* src */ + username_sz, + error)) { + g_prefix_error(error, "username invalid: "); + return FALSE; + } + + /* run transaction */ + if (!fu_ipmi_device_transaction(self, + IPMI_NETFN_APP_REQUEST, + IPMI_SET_USER_NAME, + req, + sizeof(req), + NULL, /* resp */ + 0, + NULL, + FU_IPMI_DEVICE_TIMEOUT, + error)) { + g_prefix_error(error, "failed to set user %02x name: ", user_id); + return FALSE; + } + + /* success */ + return TRUE; +} + +gboolean +fu_ipmi_device_set_user_enable(FuIpmiDevice *self, guint8 user_id, gboolean value, GError **error) +{ + guint8 op = value ? IPMI_PASSWORD_ENABLE_USER : IPMI_PASSWORD_DISABLE_USER; + const guint8 req[] = {user_id, op}; + + g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); + g_return_val_if_fail(user_id != 0x0, FALSE); + + /* run transaction */ + if (!fu_ipmi_device_transaction(self, + IPMI_NETFN_APP_REQUEST, + IPMI_SET_USER_PASSWORD, + req, + sizeof(req), + NULL, /* resp */ + 0, + NULL, + FU_IPMI_DEVICE_TIMEOUT, + error)) { + g_prefix_error(error, "failed to set user %02x enable: ", user_id); + return FALSE; + } + + /* success */ + return TRUE; +} + +gboolean +fu_ipmi_device_set_user_password(FuIpmiDevice *self, + guint8 user_id, + const gchar *password, + GError **error) +{ + guint8 req[0x12] = {user_id, IPMI_PASSWORD_SET_PASSWORD}; + gsize password_sz; + + g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); + g_return_val_if_fail(user_id != 0x0, FALSE); + g_return_val_if_fail(password != NULL, FALSE); + + /* copy into buffer */ + password_sz = strlen(password); + if (!fu_memcpy_safe(req, + sizeof(req), + 0x2, /* dst */ + (guint8 *)password, + password_sz, + 0x0, /* src */ + password_sz, + error)) { + g_prefix_error(error, "password invalid: "); + return FALSE; + } + + /* run transaction */ + if (!fu_ipmi_device_transaction(self, + IPMI_NETFN_APP_REQUEST, + IPMI_SET_USER_PASSWORD, + req, + sizeof(req), + NULL, /* resp */ + 0, + NULL, + FU_IPMI_DEVICE_TIMEOUT, + error)) { + g_prefix_error(error, "failed to set user %02x password: ", user_id); + return FALSE; + } + + /* success */ + return TRUE; +} + +gboolean +fu_ipmi_device_set_user_priv(FuIpmiDevice *self, + guint8 user_id, + guint8 priv_limit, + guint8 channel, + GError **error) +{ + const guint8 req[] = {channel, user_id, priv_limit, 0x0}; + + g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); + g_return_val_if_fail(user_id != 0x0, FALSE); + g_return_val_if_fail(channel <= 0x0F, FALSE); + g_return_val_if_fail(priv_limit <= 0x0F, FALSE); + + /* run transaction */ + if (!fu_ipmi_device_transaction(self, + IPMI_NETFN_APP_REQUEST, + IPMI_SET_USER_ACCESS, + req, + sizeof(req), + NULL, /* resp */ + 0, + NULL, + FU_IPMI_DEVICE_TIMEOUT, + error)) { + g_prefix_error(error, + "failed to set user %02x privs of 0x%02x, 0x%02x: ", + user_id, + priv_limit, + channel); + return FALSE; + } + + /* success */ + return TRUE; +} + +static void +fu_ipmi_device_init(FuIpmiDevice *self) +{ + fu_device_set_name(FU_DEVICE(self), "IPMI"); + fu_device_set_summary(FU_DEVICE(self), "Intelligent Platform Management Interface"); + fu_device_add_icon(FU_DEVICE(self), "computer"); + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); +} + +static void +fu_ipmi_device_class_init(FuIpmiDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->probe = fu_ipmi_device_probe; + klass_device->setup = fu_ipmi_device_setup; + klass_device->to_string = fu_ipmi_device_to_string; +} + +FuIpmiDevice * +fu_ipmi_device_new(FuContext *ctx) +{ + FuIpmiDevice *self; + self = g_object_new(FU_TYPE_IPMI_DEVICE, "context", ctx, "device-file", "/dev/ipmi0", NULL); + return self; +} diff --git a/plugins/redfish/fu-ipmi-device.h b/plugins/redfish/fu-ipmi-device.h new file mode 100644 index 000000000..b1f45ee0c --- /dev/null +++ b/plugins/redfish/fu-ipmi-device.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_IPMI_DEVICE (fu_ipmi_device_get_type()) +G_DECLARE_FINAL_TYPE(FuIpmiDevice, fu_ipmi_device, FU, IPMI_DEVICE, FuUdevDevice) + +FuIpmiDevice * +fu_ipmi_device_new(FuContext *ctx); +gchar * +fu_ipmi_device_get_user_password(FuIpmiDevice *self, guint8 user_id, GError **error); +gboolean +fu_ipmi_device_set_user_name(FuIpmiDevice *self, + guint8 user_id, + const gchar *username, + GError **error); +gboolean +fu_ipmi_device_set_user_password(FuIpmiDevice *self, + guint8 user_id, + const gchar *password, + GError **error); +gboolean +fu_ipmi_device_set_user_enable(FuIpmiDevice *self, guint8 user_id, gboolean enable, GError **error); +gboolean +fu_ipmi_device_set_user_priv(FuIpmiDevice *self, + guint8 user_id, + guint8 priv_limit, + guint8 channel, + GError **error); diff --git a/plugins/redfish/fu-plugin-redfish.c b/plugins/redfish/fu-plugin-redfish.c index 9ed063f9f..48cf03a8f 100644 --- a/plugins/redfish/fu-plugin-redfish.c +++ b/plugins/redfish/fu-plugin-redfish.c @@ -8,6 +8,10 @@ #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" @@ -181,6 +185,89 @@ fu_redfish_plugin_discover_smbios_table(FuPlugin *plugin, GError **error) 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) { @@ -245,6 +332,18 @@ fu_plugin_startup(FuPlugin *plugin, GError **error) } 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); } diff --git a/plugins/redfish/fu-redfish-backend.c b/plugins/redfish/fu-redfish-backend.c index f884779de..2465d31a9 100644 --- a/plugins/redfish/fu-redfish-backend.c +++ b/plugins/redfish/fu-redfish-backend.c @@ -372,6 +372,12 @@ fu_redfish_backend_set_username(FuRedfishBackend *self, const gchar *username) self->username = g_strdup(username); } +const gchar * +fu_redfish_backend_get_username(FuRedfishBackend *self) +{ + return self->username; +} + void fu_redfish_backend_set_password(FuRedfishBackend *self, const gchar *password) { diff --git a/plugins/redfish/fu-redfish-backend.h b/plugins/redfish/fu-redfish-backend.h index f22473d74..b4168ce98 100644 --- a/plugins/redfish/fu-redfish-backend.h +++ b/plugins/redfish/fu-redfish-backend.h @@ -20,6 +20,8 @@ void fu_redfish_backend_set_hostname(FuRedfishBackend *self, const gchar *hostname); void fu_redfish_backend_set_username(FuRedfishBackend *self, const gchar *username); +const gchar * +fu_redfish_backend_get_username(FuRedfishBackend *self); void fu_redfish_backend_set_password(FuRedfishBackend *self, const gchar *password); void diff --git a/plugins/redfish/fu-self-test.c b/plugins/redfish/fu-self-test.c index 482311763..665e7f660 100644 --- a/plugins/redfish/fu-self-test.c +++ b/plugins/redfish/fu-self-test.c @@ -10,6 +10,9 @@ #include "fu-context-private.h" #include "fu-device-private.h" +#ifdef HAVE_LINUX_IPMI_H +#include "fu-ipmi-device.h" +#endif #include "fu-plugin-private.h" #include "fu-redfish-common.h" #include "fu-redfish-network.h" @@ -49,6 +52,56 @@ fu_test_self_init(FuTest *self) g_assert(ret); } +static void +fu_test_redfish_ipmi_func(void) +{ +#ifdef HAVE_LINUX_IPMI_H + gboolean ret; + g_autoptr(FuIpmiDevice) device = fu_ipmi_device_new(NULL); + g_autoptr(FuDeviceLocker) locker = NULL; + g_autoptr(GError) error = NULL; + g_autofree gchar *username = NULL; + g_autofree gchar *str = NULL; + + /* sanity check */ + if (!g_file_test("/dev/ipmi0", G_FILE_TEST_EXISTS)) { + g_test_skip("no IPMI hardware"); + return; + } + + /* create device */ + locker = fu_device_locker_new(device, &error); + g_assert_no_error(error); + g_assert_nonnull(locker); + str = fu_device_to_string(FU_DEVICE(device)); + g_debug("%s", str); + + /* add user that can do redfish commands */ + if (g_getenv("FWUPD_REDFISH_SELF_TEST") == NULL) { + g_test_skip("not doing destructive tests"); + return; + } + ret = fu_ipmi_device_set_user_name(device, 0x04, "fwupd", &error); + g_assert_no_error(error); + g_assert_true(ret); + username = fu_ipmi_device_get_user_password(device, 0x04, &error); + g_assert_no_error(error); + g_assert_nonnull(username); + g_debug("username=%s", username); + ret = fu_ipmi_device_set_user_enable(device, 0x04, TRUE, &error); + g_assert_no_error(error); + g_assert_true(ret); + ret = fu_ipmi_device_set_user_priv(device, 0x04, 0x4, 1, &error); + g_assert_no_error(error); + g_assert_true(ret); + ret = fu_ipmi_device_set_user_password(device, 0x04, "Passw0rd123", &error); + g_assert_no_error(error); + g_assert_true(ret); +#else + g_test_skip("no linux/ipmi.h, so skipping"); +#endif +} + static void fu_test_redfish_common_func(void) { @@ -303,6 +356,7 @@ main(int argc, char **argv) g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); fu_test_self_init(self); + g_test_add_func("/redfish/ipmi", fu_test_redfish_ipmi_func); g_test_add_func("/redfish/common", fu_test_redfish_common_func); g_test_add_func("/redfish/common{version}", fu_test_redfish_common_version_func); g_test_add_func("/redfish/common{lenovo}", fu_test_redfish_common_lenovo_func); diff --git a/plugins/redfish/fwupd-redfish.conf b/plugins/redfish/fwupd-redfish.conf new file mode 100644 index 000000000..90bbe17b1 --- /dev/null +++ b/plugins/redfish/fwupd-redfish.conf @@ -0,0 +1,2 @@ +ipmi-si +ipmi-devintf diff --git a/plugins/redfish/meson.build b/plugins/redfish/meson.build index afc00567b..07401d03c 100644 --- a/plugins/redfish/meson.build +++ b/plugins/redfish/meson.build @@ -8,6 +8,17 @@ install_data(['redfish.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) +ipmi_src = [] +if have_linux_ipmi + ipmi_src += 'fu-ipmi-device.c' +endif + +if get_option('systemd') and have_linux_ipmi +install_data(['fwupd-redfish.conf'], + install_dir: systemd_modules_load_dir, +) +endif + shared_module('fu_plugin_redfish', fu_hash, sources : [ @@ -21,6 +32,7 @@ shared_module('fu_plugin_redfish', 'fu-redfish-network-device.c', 'fu-redfish-request.c', 'fu-redfish-smbios.c', # fuzzing + ipmi_src, ], include_directories : [ root_incdir, @@ -66,6 +78,7 @@ if get_option('tests') 'fu-redfish-network-device.c', 'fu-redfish-request.c', 'fu-redfish-smbios.c', + ipmi_src, ], include_directories : [ root_incdir, diff --git a/plugins/redfish/redfish.conf b/plugins/redfish/redfish.conf index a2c3938d7..6675d5964 100644 --- a/plugins/redfish/redfish.conf +++ b/plugins/redfish/redfish.conf @@ -12,3 +12,6 @@ # Expected value: TRUE or FALSE # Default: FALSE #CACheck= + +# Do not use IPMI KCS to create an initial user account if no SMBIOS data +IpmiDisableCreateUser=False