diff --git a/contrib/ci/README.md b/contrib/ci/README.md index 5de2020c5..947b8020e 100644 --- a/contrib/ci/README.md +++ b/contrib/ci/README.md @@ -53,6 +53,7 @@ Debian testing (cross compile s390x) * Not packaged * Tests for missing translation files +* No redfish support * Compiled under gcc * Tests with -Werror enabled * Runs local test suite using qemu-user diff --git a/contrib/ci/debian_s390x.sh b/contrib/ci/debian_s390x.sh index 69cb7d25c..5aa88a76d 100755 --- a/contrib/ci/debian_s390x.sh +++ b/contrib/ci/debian_s390x.sh @@ -12,6 +12,7 @@ meson .. \ --werror \ -Dplugin_uefi=false \ -Dplugin_dell=false \ + -Dplugin_redfish=false \ -Dplugin_synaptics=false \ -Dintrospection=false \ -Dgtkdoc=false \ diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index 38162b582..9c14c7235 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -252,6 +252,7 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg %{_libdir}/fwupd-plugins-3/libfu_plugin_dfu.so %{_libdir}/fwupd-plugins-3/libfu_plugin_ebitdo.so %{_libdir}/fwupd-plugins-3/libfu_plugin_nitrokey.so +%{_libdir}/fwupd-plugins-3/libfu_plugin_redfish.so %{_libdir}/fwupd-plugins-3/libfu_plugin_steelseries.so %if 0%{?have_dell} %{_libdir}/fwupd-plugins-3/libfu_plugin_synapticsmst.so diff --git a/meson.build b/meson.build index 5835a42be..0ddca4004 100644 --- a/meson.build +++ b/meson.build @@ -201,6 +201,11 @@ if valgrind.found() conf.set('HAVE_VALGRIND', '1') endif +if get_option('plugin_redfish') + json_glib = dependency('json-glib-1.0') + efivar = dependency('efivar') +endif + if get_option('plugin_altos') libelf = dependency('libelf') endif diff --git a/meson_options.txt b/meson_options.txt index 6f35d800c..066d0f310 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -12,6 +12,7 @@ option('plugin_dell', type : 'boolean', value : true, description : 'enable Dell option('plugin_dummy', type : 'boolean', value : false, description : 'enable the dummy device') option('plugin_synaptics', type: 'boolean', value: true, description : 'enable Synaptics MST hub support') option('plugin_thunderbolt', type : 'boolean', value : true, description : 'enable Thunderbolt support') +option('plugin_redfish', type : 'boolean', value : true, description : 'enable Redfish support') option('plugin_uefi', type : 'boolean', value : true, description : 'enable UEFI support') option('systemd', type : 'boolean', value : true, description : 'enable systemd support') option('systemdunitdir', type: 'string', value: '', description: 'Directory for systemd units') diff --git a/plugins/meson.build b/plugins/meson.build index 9ba08c8bf..d5ad0f4a2 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -25,6 +25,10 @@ subdir('thunderbolt') subdir('thunderbolt-power') endif +if get_option('plugin_redfish') +subdir('redfish') +endif + if get_option('plugin_dell') subdir('dell') subdir('dell-esrt') diff --git a/plugins/redfish/README.md b/plugins/redfish/README.md new file mode 100644 index 000000000..96fdfcc1b --- /dev/null +++ b/plugins/redfish/README.md @@ -0,0 +1,11 @@ +Redfish Support +=============== + +Introduction +------------ + +Redfish is an open industry standard specification and schema that helps enable +simple and secure management of modern scalable platform hardware. + +By specifying a RESTful interface and utilizing JSON and OData, Redfish helps +customers integrate solutions within their existing tool chains. diff --git a/plugins/redfish/fu-plugin-redfish.c b/plugins/redfish/fu-plugin-redfish.c new file mode 100644 index 000000000..ccf74df2c --- /dev/null +++ b/plugins/redfish/fu-plugin-redfish.c @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017-2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-plugin.h" +#include "fu-plugin-vfuncs.h" + +#include "fu-redfish-client.h" +#include "fu-redfish-common.h" + +struct FuPluginData { + FuRedfishClient *client; +}; + +gboolean +fu_plugin_coldplug (FuPlugin *plugin, GError **error) +{ + FuPluginData *data = fu_plugin_get_data (plugin); + GPtrArray *devices; + + /* get the list of devices */ + if (!fu_redfish_client_coldplug (data->client, error)) + return FALSE; + devices = fu_redfish_client_get_devices (data->client); + for (guint i = 0; i < devices->len; i++) { + FuDevice *device = g_ptr_array_index (devices, i); + fu_plugin_device_add (plugin, device); + } + return TRUE; +} + +gboolean +fu_plugin_startup (FuPlugin *plugin, GError **error) +{ + FuPluginData *data = fu_plugin_get_data (plugin); + GBytes *smbios_data = fu_plugin_get_smbios_data (plugin, REDFISH_SMBIOS_TABLE_TYPE); + const gchar *redfish_uri; + + /* using the emulator */ + redfish_uri = g_getenv ("FWUPD_REDFISH_URI"); + if (redfish_uri != NULL) { + guint64 port; + g_auto(GStrv) split = g_strsplit (redfish_uri, ":", 2); + fu_redfish_client_set_hostname (data->client, 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_client_set_port (data->client, port); + } else { + if (smbios_data == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "no SMBIOS table"); + return FALSE; + } + } + return fu_redfish_client_setup (data->client, smbios_data, error); +} + +void +fu_plugin_init (FuPlugin *plugin) +{ + FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData)); + data->client = fu_redfish_client_new (); +} + +void +fu_plugin_destroy (FuPlugin *plugin) +{ + FuPluginData *data = fu_plugin_get_data (plugin); + g_object_unref (data->client); +} diff --git a/plugins/redfish/fu-redfish-client.c b/plugins/redfish/fu-redfish-client.c new file mode 100644 index 000000000..21f30d1af --- /dev/null +++ b/plugins/redfish/fu-redfish-client.c @@ -0,0 +1,592 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017-2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include +#include +#include + +#include "fwupd-error.h" +#include "fwupd-enums.h" + +#include "fu-device.h" + +#include "fu-redfish-client.h" +#include "fu-redfish-common.h" + +struct _FuRedfishClient +{ + GObject parent_instance; + SoupSession *session; + gchar *hostname; + guint port; + gchar *username; + gchar *password; + gchar *update_uri_path; + GPtrArray *devices; +}; + +G_DEFINE_TYPE (FuRedfishClient, fu_redfish_client, G_TYPE_OBJECT) + +static GBytes * +fu_redfish_client_fetch_data (FuRedfishClient *self, const gchar *uri_path, GError **error) +{ + guint status_code; + g_autoptr(SoupMessage) msg = NULL; + g_autoptr(SoupURI) uri = NULL; + + /* create URI */ + uri = soup_uri_new (NULL); + soup_uri_set_scheme (uri, g_strcmp0 (self->hostname, "localhost") == 0 ? + "http" : "https"); + soup_uri_set_path (uri, uri_path); + soup_uri_set_host (uri, self->hostname); + soup_uri_set_user (uri, self->username); + soup_uri_set_port (uri, self->port); + soup_uri_set_password (uri, self->password); + msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); + if (msg == NULL) { + g_autofree gchar *tmp = soup_uri_to_string (uri, FALSE); + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "failed to create message for URI %s", tmp); + return NULL; + } + status_code = soup_session_send_message (self->session, msg); + if (status_code != SOUP_STATUS_OK) { + g_autofree gchar *tmp = soup_uri_to_string (uri, FALSE); + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "failed to download %s: %s", + tmp, soup_status_get_phrase (status_code)); + return NULL; + } + return g_bytes_new (msg->response_body->data, msg->response_body->length); +} + +static gboolean +fu_redfish_client_coldplug_member (FuRedfishClient *self, + JsonObject *member, + GError **error) +{ + g_autoptr(FuDevice) dev = fu_device_new (); + + fu_device_set_name (dev, json_object_get_string_member (member, "Name")); + fu_device_set_summary (dev, "Redfish device"); + fu_device_set_version (dev, json_object_get_string_member (member, "Version")); + if (json_object_has_member (member, "LowestSupportedVersion")) + fu_device_set_version_lowest (dev, json_object_get_string_member (member, "LowestSupportedVersion")); + if (json_object_has_member (member, "Description")) + fu_device_set_description (dev, json_object_get_string_member (member, "Description")); + if (json_object_get_boolean_member (member, "Updateable")) + fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_add_guid (dev, json_object_get_string_member (member, "SoftwareId")); + + /* success */ + g_ptr_array_add (self->devices, g_steal_pointer (&dev)); + return TRUE; +} + +static gboolean +fu_redfish_client_coldplug_inventory (FuRedfishClient *self, + JsonObject *inventory, + GError **error) +{ + JsonArray *members; + members = json_object_get_array_member (inventory, "Members"); + for (guint i = 0; i < json_array_get_length (members); i++) { + JsonObject *member = json_array_get_object_element (members, i); + if (!fu_redfish_client_coldplug_member (self, member, error)) + return FALSE; + } + return TRUE; +} + +gboolean +fu_redfish_client_coldplug (FuRedfishClient *self, GError **error) +{ + JsonNode *node_root; + JsonObject *obj_root = NULL; + g_autoptr(GBytes) blob = NULL; + g_autoptr(JsonParser) parser = json_parser_new (); + + /* nothing set */ + if (self->update_uri_path == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "no update_uri_path"); + return FALSE; + } + + /* try to connect */ + blob = fu_redfish_client_fetch_data (self, self->update_uri_path, error); + if (blob == NULL) + return FALSE; + + /* get the update service */ + if (!json_parser_load_from_data (parser, + g_bytes_get_data (blob, NULL), + (gssize) g_bytes_get_size (blob), + error)) { + g_prefix_error (error, "failed to parse node: "); + return FALSE; + } + node_root = json_parser_get_root (parser); + if (node_root == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "no root node"); + return FALSE; + } + obj_root = json_node_get_object (node_root); + if (obj_root == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "no root object"); + return FALSE; + } + if (!json_object_get_boolean_member (obj_root, "ServiceEnabled")) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "service is not enabled"); + return FALSE; + } + if (json_object_has_member (obj_root, "FirmwareInventory")) { + JsonObject *tmp = json_object_get_object_member (obj_root, "FirmwareInventory"); + return fu_redfish_client_coldplug_inventory (self, tmp, error); + } + if (json_object_has_member (obj_root, "SoftwareInventory")) { + JsonObject *tmp = json_object_get_object_member (obj_root, "SoftwareInventory"); + return fu_redfish_client_coldplug_inventory (self, tmp, error); + } + return TRUE; +} + +static gboolean +fu_redfish_client_set_uefi_credentials (FuRedfishClient *self, GError **error) +{ + guint32 indications_le; + g_autofree gchar *userpass_safe = NULL; + g_auto(GStrv) split = NULL; + g_autoptr(GBytes) indications = NULL; + g_autoptr(GBytes) userpass = NULL; + + /* get the uint32 specifying if there are EFI variables set */ + indications = fu_redfish_common_get_evivar_raw (REDFISH_EFI_INFORMATION_GUID, + REDFISH_EFI_INFORMATION_INDICATIONS, + error); + if (indications == NULL) + return FALSE; + if (g_bytes_get_size (indications) != 4) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "invalid value for %s, got %" G_GSIZE_FORMAT " bytes", + REDFISH_EFI_INFORMATION_INDICATIONS, + g_bytes_get_size (indications)); + return FALSE; + } + memcpy (&indications_le, g_bytes_get_data (indications, NULL), 4); + if ((indications_le & 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_redfish_common_get_evivar_raw (REDFISH_EFI_INFORMATION_GUID, + REDFISH_EFI_INFORMATION_OS_CREDENTIALS, + 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_client_set_username (self, split[0]); + fu_redfish_client_set_password (self, split[1]); + return TRUE; +} + +static void +fu_redfish_client_parse_interface_data (const guint8 *buf, guint8 sz) +{ + switch (buf[0]) { + case REDFISH_INTERFACE_TYPE_USB_NEWORK: + g_debug ("USB Network Interface"); + /* + * uint16 idVendor(2-bytes) + * uint16 idProduct(2-bytes) + * uint8 SerialNumberLen: + * uint8 DescriptorType: + * uint8* SerialNumber: + */ + break; + case REDFISH_INTERFACE_TYPE_PCI_NEWORK: + g_debug ("PCI Network Interface"); + /* + * uint16 VendorID + * uint16 DeviceID + * uint16 Subsystem_Vendor_ID + * uint16 Subsystem_ID + */ + break; + default: + break; + } +} + +typedef struct __attribute__((packed)) { + guint8 service_uuid[16]; + guint8 host_ip_assignment_type; + guint8 host_ip_address_format; + guint8 host_ip_address[16]; + guint8 host_ip_mask[16]; + guint8 service_ip_assignment_type; + guint8 service_ip_address_format; + guint8 service_ip_address[16]; + guint8 service_ip_mask[16]; + guint16 service_ip_port; + guint32 service_ip_vlan_id; + guint8 service_hostname_len; + /* service_hostname; */ +} RedfishProtocolDataOverIp; + +static gboolean +fu_redfish_client_parse_protocol_data (FuRedfishClient *self, + const guint8 *buf, + guint8 sz, + GError **error) +{ + RedfishProtocolDataOverIp *pr; + if (sz < sizeof(RedfishProtocolDataOverIp)) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "protocol data too small"); + return FALSE; + } + pr = (RedfishProtocolDataOverIp *) buf; + + /* parse the hostname and port */ + if (pr->service_ip_assignment_type == REDFISH_IP_ASSIGNMENT_TYPE_STATIC || + pr->service_ip_assignment_type == REDFISH_IP_ASSIGNMENT_TYPE_AUTO_CONFIG) { + if (pr->service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V4) { + g_autofree gchar *tmp = NULL; + tmp = fu_redfish_common_buffer_to_ipv4 (pr->service_ip_address); + fu_redfish_client_set_hostname (self, tmp); + } else if (pr->service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V6) { + g_autofree gchar *tmp = NULL; + tmp = fu_redfish_common_buffer_to_ipv6 (pr->service_ip_address); + fu_redfish_client_set_hostname (self, tmp); + } else { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "address format is invalid"); + return FALSE; + } + fu_redfish_client_set_port (self, pr->service_ip_port); + } else { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "DHCP address formats not supported (%0x2)", + pr->service_ip_assignment_type); + return FALSE; + } + + return TRUE; +} + +static gboolean +fu_redfish_client_set_smbios_interfaces (FuRedfishClient *self, + GBytes *smbios_table, + GError **error) +{ + const guint8 *buf; + gsize sz = 0; + + /* check size */ + buf = g_bytes_get_data (smbios_table, &sz); + if (sz < 0x09) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "SMBIOS entry too small: %" G_GSIZE_FORMAT, + sz); + return FALSE; + } + + /* check interface type */ + if (buf[0x04] != REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "only Network Host Interface supported"); + return FALSE; + } + + /* check length */ + if (buf[0x05] > sz - 0x08) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "interface specific data too large %u > %" G_GSIZE_FORMAT, + buf[0x05], sz - 0x08); + return FALSE; + } + + /* parse data, for not just for debugging */ + if (buf[0x05] > 0) + fu_redfish_client_parse_interface_data (&buf[0x06], buf[0x05]); + + /* parse protocol records */ + for (guint8 i = 0x07 + buf[0x05]; i < sz - 1; i++) { + guint8 protocol_id = buf[i]; + guint8 protocol_sz = buf[i+1]; + if (protocol_sz > sz - buf[0x05] + 0x07) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "protocol length too large"); + return FALSE; + } + if (protocol_id == REDFISH_PROTOCOL_REDFISH_OVER_IP) { + if (!fu_redfish_client_parse_protocol_data (self, + &buf[i+2], + protocol_sz, + error)) + return FALSE; + } else { + g_debug ("ignoring unsupported protocol ID %02x", + protocol_id); + } + i += protocol_sz - 1; + } + + return TRUE; +} + +gboolean +fu_redfish_client_setup (FuRedfishClient *self, GBytes *smbios_table, GError **error) +{ + JsonNode *node_root; + JsonObject *obj_links = NULL; + JsonObject *obj_root = NULL; + JsonObject *obj_update_service = NULL; + const gchar *data_id; + g_autofree gchar *user_agent = NULL; + g_autoptr(GBytes) blob = NULL; + g_autoptr(JsonParser) parser = json_parser_new (); + + /* sanity check */ + if (self->port == 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "no port specified"); + return FALSE; + } + if (self->port > 0xffff) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "invalid port specified: 0x%x", + self->port); + return FALSE; + } + + /* create the soup session */ + user_agent = g_strdup_printf ("%s/%s", PACKAGE_NAME, PACKAGE_VERSION); + self->session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent, + SOUP_SESSION_TIMEOUT, 60, + NULL); + if (self->session == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to setup networking"); + return FALSE; + } + + /* this is optional */ + if (smbios_table != NULL) { + g_autoptr(GError) error_smbios = NULL; + g_autoptr(GError) error_uefi = NULL; + if (!fu_redfish_client_set_smbios_interfaces (self, + smbios_table, + &error_smbios)) { + g_debug ("failed to get connection URI automatically: %s", + error_smbios->message); + } + if (!fu_redfish_client_set_uefi_credentials (self, &error_uefi)) { + g_debug ("failed to get username and password automatically: %s", + error_uefi->message); + } + } + if (self->hostname != NULL) + g_debug ("Hostname: %s", self->hostname); + if (self->port != 0) + g_debug ("Port: %u", self->port); + if (self->username != NULL) + g_debug ("Username: %s", self->username); + if (self->password != NULL) + g_debug ("Password: %s", self->password); + + /* try to connect */ + blob = fu_redfish_client_fetch_data (self, "/redfish/v1", error); + if (blob == NULL) + return FALSE; + + /* get the update service */ + if (!json_parser_load_from_data (parser, + g_bytes_get_data (blob, NULL), + (gssize) g_bytes_get_size (blob), + error)) { + g_prefix_error (error, "failed to parse node: "); + return FALSE; + } + node_root = json_parser_get_root (parser); + if (node_root == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "no root node"); + return FALSE; + } + obj_root = json_node_get_object (node_root); + if (obj_root == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "no root object"); + return FALSE; + } + g_debug ("Version: %s", + json_object_get_string_member (obj_root, "ServiceVersion")); + g_debug ("UUID: %s", + json_object_get_string_member (obj_root, "UUID")); + + /* look for UpdateService in Links */ + if (json_object_has_member (obj_root, "Links")) + obj_links = json_object_get_object_member (obj_root, "Links"); + if (obj_links == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "no Links object"); + return FALSE; + } + if (json_object_has_member (obj_links, "UpdateService")) + obj_update_service = json_object_get_object_member (obj_links, "UpdateService"); + if (obj_update_service == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "no UpdateService object"); + return FALSE; + } + data_id = json_object_get_string_member (obj_update_service, "@odata.id"); + if (data_id == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "no @odata.id string"); + return FALSE; + } + self->update_uri_path = g_strdup (data_id); + return TRUE; +} + +GPtrArray * +fu_redfish_client_get_devices (FuRedfishClient *self) +{ + return self->devices; +} + +void +fu_redfish_client_set_hostname (FuRedfishClient *self, const gchar *hostname) +{ + g_free (self->hostname); + self->hostname = g_strdup (hostname); +} + +void +fu_redfish_client_set_port (FuRedfishClient *self, guint port) +{ + self->port = port; +} + +void +fu_redfish_client_set_username (FuRedfishClient *self, const gchar *username) +{ + g_free (self->username); + self->username = g_strdup (username); +} + +void +fu_redfish_client_set_password (FuRedfishClient *self, const gchar *password) +{ + g_free (self->password); + self->password = g_strdup (password); +} + +static void +fu_redfish_client_finalize (GObject *object) +{ + FuRedfishClient *self = FU_REDFISH_CLIENT (object); + if (self->session != NULL) + g_object_unref (self->session); + g_free (self->update_uri_path); + g_free (self->hostname); + g_free (self->username); + g_free (self->password); + g_ptr_array_unref (self->devices); + G_OBJECT_CLASS (fu_redfish_client_parent_class)->finalize (object); +} + +static void +fu_redfish_client_class_init (FuRedfishClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = fu_redfish_client_finalize; +} + +static void +fu_redfish_client_init (FuRedfishClient *self) +{ + self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); +} + +FuRedfishClient * +fu_redfish_client_new (void) +{ + FuRedfishClient *self; + self = g_object_new (REDFISH_TYPE_CLIENT, NULL); + return FU_REDFISH_CLIENT (self); +} + +/* vim: set noexpandtab: */ diff --git a/plugins/redfish/fu-redfish-client.h b/plugins/redfish/fu-redfish-client.h new file mode 100644 index 000000000..d5196a19a --- /dev/null +++ b/plugins/redfish/fu-redfish-client.h @@ -0,0 +1,40 @@ + /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017-2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef __FU_REDFISH_CLIENT_H +#define __FU_REDFISH_CLIENT_H + +#include +#include + +G_BEGIN_DECLS + +#define REDFISH_TYPE_CLIENT (fu_redfish_client_get_type ()) + +G_DECLARE_FINAL_TYPE (FuRedfishClient, fu_redfish_client, FU, REDFISH_CLIENT, GObject) + +FuRedfishClient *fu_redfish_client_new (void); +void fu_redfish_client_set_hostname (FuRedfishClient *self, + const gchar *hostname); +void fu_redfish_client_set_username (FuRedfishClient *self, + const gchar *username); +void fu_redfish_client_set_password (FuRedfishClient *self, + const gchar *password); +void fu_redfish_client_set_port (FuRedfishClient *self, + guint port); +gboolean fu_redfish_client_setup (FuRedfishClient *self, + GBytes *smbios_table, + GError **error); +gboolean fu_redfish_client_coldplug (FuRedfishClient *self, + GError **error); +GPtrArray *fu_redfish_client_get_devices (FuRedfishClient *self); + +G_END_DECLS + +#endif /* __FU_REDFISH_CLIENT_H */ + +/* vim: set noexpandtab: */ diff --git a/plugins/redfish/fu-redfish-common.c b/plugins/redfish/fu-redfish-common.c new file mode 100644 index 000000000..03d3b61a0 --- /dev/null +++ b/plugins/redfish/fu-redfish-common.c @@ -0,0 +1,60 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017-2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fwupd-error.h" + +#include "fu-redfish-common.h" + +GBytes * +fu_redfish_common_get_evivar_raw (efi_guid_t guid, const gchar *name, GError **error) +{ + gsize sz = 0; + guint32 attribs = 0; + guint8 *data = NULL; + + if (efi_get_variable (guid, name, &data, &sz, &attribs) < 0) { + g_autofree gchar *guid_str = NULL; + efi_guid_to_str (&guid, &guid_str); + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "failed to get efivar for %s %s", + guid_str, name); + return NULL; + } + return g_bytes_new_take (data, sz); +} + +gchar * +fu_redfish_common_buffer_to_ipv4 (const guint8 *buffer) +{ + GString *str = g_string_new (NULL); + for (guint i = 0; i < 4; i++) { + g_string_append_printf (str, "%u", buffer[i]); + if (i != 3) + g_string_append (str, "."); + } + return g_string_free (str, FALSE); +} + +gchar * +fu_redfish_common_buffer_to_ipv6 (const guint8 *buffer) +{ + GString *str = g_string_new (NULL); + for (guint i = 0; i < 16; i += 4) { + g_string_append_printf (str, "%02x%02x%02x%02x", + buffer[i+0], buffer[i+1], + buffer[i+2], buffer[i+3]); + if (i != 12) + g_string_append (str, ":"); + } + return g_string_free (str, FALSE); +} + +/* vim: set noexpandtab: */ diff --git a/plugins/redfish/fu-redfish-common.h b/plugins/redfish/fu-redfish-common.h new file mode 100644 index 000000000..8518e8bf7 --- /dev/null +++ b/plugins/redfish/fu-redfish-common.h @@ -0,0 +1,56 @@ + /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017-2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef __FU_REDFISH_COMMON_H +#define __FU_REDFISH_COMMON_H + +#include +#include + +G_BEGIN_DECLS + +/* SMBIOS */ +#define REDFISH_SMBIOS_TABLE_TYPE 0x42 + +#define REDFISH_PROTOCOL_REDFISH_OVER_IP 0x04 + +#define REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST 0x40 + +#define REDFISH_INTERFACE_TYPE_USB_NEWORK 0x02 +#define REDFISH_INTERFACE_TYPE_PCI_NEWORK 0x03 + +#define REDFISH_IP_ASSIGNMENT_TYPE_STATIC 0x00 +#define REDFISH_IP_ASSIGNMENT_TYPE_DHCP 0x02 +#define REDFISH_IP_ASSIGNMENT_TYPE_AUTO_CONFIG 0x03 +#define REDFISH_IP_ASSIGNMENT_TYPE_HOST_SELECT 0x04 + +#define REDFISH_IP_ADDRESS_FORMAT_UNKNOWN 0x00 +#define REDFISH_IP_ADDRESS_FORMAT_V4 0x01 +#define REDFISH_IP_ADDRESS_FORMAT_V6 0x02 + +/* EFI */ +#define REDFISH_EFI_INFORMATION_GUID EFI_GUID(0x16faa37e,0x4b6a,0x4891,0x9028,0x24,0x2d,0xe6,0x5a,0x3b,0x70) + +#define REDFISH_EFI_INFORMATION_INDICATIONS "RedfishIndications" +#define REDFISH_EFI_INFORMATION_FW_CREDENTIALS "RedfishFWCredentials" +#define REDFISH_EFI_INFORMATION_OS_CREDENTIALS "RedfishOSCredentials" + +#define REDFISH_EFI_INDICATIONS_FW_CREDENTIALS 0x00000001 +#define REDFISH_EFI_INDICATIONS_OS_CREDENTIALS 0x00000002 + +/* shared */ +GBytes *fu_redfish_common_get_evivar_raw (efi_guid_t guid, + const gchar *name, + GError **error); +gchar *fu_redfish_common_buffer_to_ipv4 (const guint8 *buffer); +gchar *fu_redfish_common_buffer_to_ipv6 (const guint8 *buffer); + +G_END_DECLS + +#endif /* __FU_REDFISH_COMMON_H */ + +/* vim: set noexpandtab: */ diff --git a/plugins/redfish/fu-self-test.c b/plugins/redfish/fu-self-test.c new file mode 100644 index 000000000..765159931 --- /dev/null +++ b/plugins/redfish/fu-self-test.c @@ -0,0 +1,38 @@ +/* -*- mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017-2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-plugin-private.h" +#include "fu-test.h" + +#include "fu-redfish-common.h" + +static void +fu_test_redfish_common_func (void) +{ + const guint8 buf[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; + g_autofree gchar *ipv4 = NULL; + g_autofree gchar *ipv6 = NULL; + + ipv4 = fu_redfish_common_buffer_to_ipv4 (buf); + g_assert_cmpstr (ipv4, ==, "0.1.2.3"); + ipv6 = fu_redfish_common_buffer_to_ipv6 (buf); + g_assert_cmpstr (ipv6, ==, "00010203:04050607:08090a0b:0c0d0e0f"); +} + +int +main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); + g_test_add_func ("/redfish/common", fu_test_redfish_common_func); + return g_test_run (); +} diff --git a/plugins/redfish/meson.build b/plugins/redfish/meson.build new file mode 100644 index 000000000..5442fc4d4 --- /dev/null +++ b/plugins/redfish/meson.build @@ -0,0 +1,49 @@ +cargs = ['-DG_LOG_DOMAIN="FuPluginRedfish"'] + +shared_module('fu_plugin_redfish', + sources : [ + 'fu-plugin-redfish.c', + 'fu-redfish-client.c', + 'fu-redfish-common.c', + ], + include_directories : [ + include_directories('../..'), + include_directories('../../src'), + include_directories('../../libfwupd'), + ], + install : true, + install_dir: plugin_dir, + c_args : cargs, + dependencies : [ + plugin_deps, + efivar, + json_glib, + ], +) + +if get_option('tests') + e = executable( + 'redfish-self-test', + sources : [ + 'fu-self-test.c', + 'fu-redfish-client.c', + 'fu-redfish-common.c', + ], + include_directories : [ + include_directories('../..'), + include_directories('../../src'), + include_directories('../../libfwupd'), + ], + dependencies : [ + plugin_deps, + efivar, + json_glib, + ], + link_with : [ + fwupd, + libfwupdprivate, + ], + c_args : cargs + ) + test('redfish-self-test', e) +endif