mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-15 13:59:15 +00:00
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.
This commit is contained in:
parent
fb3f869810
commit
1210aa4ae7
@ -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
|
||||
|
12
meson.build
12
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
|
||||
|
593
plugins/redfish/fu-ipmi-device.c
Normal file
593
plugins/redfish/fu-ipmi-device.c
Normal file
@ -0,0 +1,593 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <linux/ipmi.h>
|
||||
#include <linux/ipmi_msgdefs.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
35
plugins/redfish/fu-ipmi-device.h
Normal file
35
plugins/redfish/fu-ipmi-device.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#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);
|
@ -8,6 +8,10 @@
|
||||
|
||||
#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"
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
2
plugins/redfish/fwupd-redfish.conf
Normal file
2
plugins/redfish/fwupd-redfish.conf
Normal file
@ -0,0 +1,2 @@
|
||||
ipmi-si
|
||||
ipmi-devintf
|
@ -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,
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user