From 1210aa4ae7a8bddfe4d0071736c1285f17136faa Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 6 Sep 2021 15:09:29 +0100 Subject: [PATCH] redfish: Create user accounts automatically using IPMI 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. --- contrib/fwupd.spec.in | 1 + meson.build | 12 + plugins/redfish/fu-ipmi-device.c | 593 +++++++++++++++++++++++++++ plugins/redfish/fu-ipmi-device.h | 35 ++ plugins/redfish/fu-plugin-redfish.c | 99 +++++ plugins/redfish/fu-redfish-backend.c | 6 + plugins/redfish/fu-redfish-backend.h | 2 + plugins/redfish/fu-self-test.c | 54 +++ plugins/redfish/fwupd-redfish.conf | 2 + plugins/redfish/meson.build | 13 + plugins/redfish/redfish.conf | 3 + 11 files changed, 820 insertions(+) create mode 100644 plugins/redfish/fu-ipmi-device.c create mode 100644 plugins/redfish/fu-ipmi-device.h create mode 100644 plugins/redfish/fwupd-redfish.conf 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