fwupd/plugins/redfish/fu-redfish-backend.c
2021-08-06 20:51:22 +01:00

443 lines
13 KiB
C

/*
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include <fwupdplugin.h>
#include "fu-redfish-backend.h"
#include "fu-redfish-common.h"
#include "fu-redfish-multipart-device.h"
#include "fu-redfish-legacy-device.h"
#include "fu-redfish-request.h"
#include "fu-redfish-smbios.h"
struct _FuRedfishBackend
{
FuBackend parent_instance;
gchar *hostname;
gchar *username;
gchar *password;
guint port;
gchar *update_uri_path;
gchar *push_uri_path;
gboolean use_https;
gboolean cacheck;
gboolean wildcard_targets;
gint64 max_image_size; /* bytes */
GType device_gtype;
GHashTable *request_cache; /* str:GByteArray */
CURLSH *curlsh;
};
G_DEFINE_TYPE (FuRedfishBackend, fu_redfish_backend, FU_TYPE_BACKEND)
FuRedfishRequest *
fu_redfish_backend_request_new (FuRedfishBackend *self)
{
FuRedfishRequest *request = g_object_new (FU_TYPE_REDFISH_REQUEST, NULL);
CURL *curl;
#ifdef HAVE_LIBCURL_7_62_0
CURLU *uri;
#else
g_autofree gchar *uri_base = NULL;
#endif
g_autofree gchar *user_agent = NULL;
g_autofree gchar *port = g_strdup_printf ("%u", self->port);
/* set the cache location */
fu_redfish_request_set_cache (request, self->request_cache);
fu_redfish_request_set_curlsh (request, self->curlsh);
/* set up defaults */
curl = fu_redfish_request_get_curl (request);
#ifdef HAVE_LIBCURL_7_62_0
uri = fu_redfish_request_get_uri (request);
curl_url_set (uri, CURLUPART_SCHEME, self->use_https ? "https" : "http", 0);
curl_url_set (uri, CURLUPART_HOST, self->hostname, 0);
curl_url_set (uri, CURLUPART_PORT, port, 0);
curl_easy_setopt (curl, CURLOPT_CURLU, uri);
#else
uri_base = g_strdup_printf ("%s://%s:%s",
self->use_https ? "https" : "http",
self->hostname,
port);
fu_redfish_request_set_uri_base (request, uri_base);
#endif
/* since DSP0266 makes Basic Authorization a requirement,
* it is safe to use Basic Auth for all implementations */
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, (glong)CURLAUTH_BASIC);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (glong)180);
curl_easy_setopt(curl, CURLOPT_USERNAME, self->username);
curl_easy_setopt(curl, CURLOPT_PASSWORD, self->password);
/* setup networking */
user_agent = g_strdup_printf ("%s/%s", PACKAGE_NAME, PACKAGE_VERSION);
curl_easy_setopt (curl, CURLOPT_USERAGENT , user_agent);
curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 60L);
if (!self->cacheck)
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
/* success */
return request;
}
static gboolean
fu_redfish_backend_coldplug_member (FuRedfishBackend *self,
JsonObject *member,
GError **error)
{
g_autoptr(FuDevice) dev = NULL;
g_autoptr(FuDeviceLocker) locker = NULL;
/* create of the correct type */
dev = g_object_new (self->device_gtype,
"context", fu_backend_get_context (FU_BACKEND (self)),
"backend", self,
"member", member,
NULL);
/* some vendors do not specify the Targets array when updating */
if (self->wildcard_targets)
fu_device_add_private_flag (dev, FU_REDFISH_DEVICE_FLAG_WILDCARD_TARGETS);
/* probe + setup */
locker = fu_device_locker_new (dev, error);
if (locker == NULL)
return FALSE;
if (self->max_image_size != 0)
fu_device_set_firmware_size_max (dev, (guint64) self->max_image_size);
fu_backend_device_added (FU_BACKEND (self), dev);
return TRUE;
}
static gboolean
fu_redfish_backend_coldplug_collection (FuRedfishBackend *self,
JsonObject *collection,
GError **error)
{
JsonArray *members = json_object_get_array_member (collection, "Members");
for (guint i = 0; i < json_array_get_length (members); i++) {
JsonObject *json_obj;
JsonObject *member_id;
const gchar *member_uri;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
member_id = json_array_get_object_element (members, i);
member_uri = json_object_get_string_member (member_id, "@odata.id");
if (member_uri == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no @odata.id string");
return FALSE;
}
/* create the device for the member */
if (!fu_redfish_request_perform (request,
member_uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
if (!fu_redfish_backend_coldplug_member (self, json_obj, error))
return FALSE;
}
return TRUE;
}
static gboolean
fu_redfish_backend_coldplug_inventory (FuRedfishBackend *self,
JsonObject *inventory,
GError **error)
{
JsonObject *json_obj;
const gchar *collection_uri;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
if (inventory == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no inventory object");
return FALSE;
}
collection_uri = json_object_get_string_member (inventory, "@odata.id");
if (collection_uri == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no @odata.id string");
return FALSE;
}
if (!fu_redfish_request_perform (request,
collection_uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
return fu_redfish_backend_coldplug_collection (self, json_obj, error);
}
static void
fu_redfish_backend_check_wildcard_targets (FuRedfishBackend *self)
{
g_autoptr(GPtrArray) devices = fu_backend_get_devices (FU_BACKEND (self));
g_autoptr(GHashTable) device_by_id0 = g_hash_table_new (g_str_hash, g_str_equal);
/* does the SoftwareId exist from a different device */
for (guint i = 0; i < devices->len; i++) {
FuDevice *device_old;
FuDevice *device_tmp = g_ptr_array_index (devices, i);
GPtrArray *ids =fu_device_get_instance_ids (device_tmp);
const gchar *id0 = g_ptr_array_index (ids, 0);
device_old = g_hash_table_lookup (device_by_id0, id0);
if (device_old == NULL) {
g_hash_table_insert (device_by_id0, (gpointer) device_tmp, (gpointer) id0);
continue;
}
fu_device_add_flag (device_tmp, FWUPD_DEVICE_FLAG_WILDCARD_INSTALL);
fu_device_add_flag (device_old, FWUPD_DEVICE_FLAG_WILDCARD_INSTALL);
}
}
static gboolean
fu_redfish_backend_coldplug (FuBackend *backend, GError **error)
{
FuRedfishBackend *self = FU_REDFISH_BACKEND (backend);
JsonObject *json_obj;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
/* nothing set */
if (self->update_uri_path == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"no update_uri_path");
return FALSE;
}
/* get the update service */
if (!fu_redfish_request_perform (request,
self->update_uri_path,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
if (!json_object_has_member (json_obj, "ServiceEnabled")) {
if (!json_object_get_boolean_member (json_obj, "ServiceEnabled")) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"service is not enabled");
return FALSE;
}
}
if (json_object_has_member (json_obj, "MultipartHttpPushUri")) {
const gchar *tmp = json_object_get_string_member (json_obj, "MultipartHttpPushUri");
if (tmp != NULL) {
self->device_gtype = FU_TYPE_REDFISH_MULTIPART_DEVICE;
self->push_uri_path = g_strdup (tmp);
}
}
if (self->push_uri_path == NULL &&
json_object_has_member (json_obj, "HttpPushUri")) {
const gchar *tmp = json_object_get_string_member (json_obj, "HttpPushUri");
if (tmp != NULL) {
self->device_gtype = FU_TYPE_REDFISH_LEGACY_DEVICE;
self->push_uri_path = g_strdup (tmp);
}
}
if (self->push_uri_path == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"HttpPushUri and MultipartHttpPushUri are invalid");
return FALSE;
}
if (json_object_has_member (json_obj, "MaxImageSizeBytes")) {
self->max_image_size =
json_object_get_int_member (json_obj, "MaxImageSizeBytes");
}
if (json_object_has_member (json_obj, "FirmwareInventory")) {
JsonObject *tmp = json_object_get_object_member (json_obj, "FirmwareInventory");
return fu_redfish_backend_coldplug_inventory (self, tmp, error);
}
if (json_object_has_member (json_obj, "SoftwareInventory")) {
JsonObject *tmp = json_object_get_object_member (json_obj, "SoftwareInventory");
return fu_redfish_backend_coldplug_inventory (self, tmp, error);
}
/* work out if we have multiple devices with the same SoftwareId */
if (self->wildcard_targets)
fu_redfish_backend_check_wildcard_targets (self);
/* success */
return TRUE;
}
static gboolean
fu_redfish_backend_setup (FuBackend *backend, GError **error)
{
FuRedfishBackend *self = FU_REDFISH_BACKEND (backend);
JsonObject *json_obj;
JsonObject *json_update_service = NULL;
const gchar *data_id;
const gchar *version = NULL;
const gchar *uuid = NULL;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
/* sanity check */
if (self->port == 0 || self->port > G_MAXUINT16) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"invalid port specified: 0x%x",
self->port);
return FALSE;
}
/* try to connect */
if (!fu_redfish_request_perform (request,
"/redfish/v1/",
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
if (json_object_has_member (json_obj, "ServiceVersion")) {
version = json_object_get_string_member (json_obj, "ServiceVersion");
} else if (json_object_has_member (json_obj, "RedfishVersion")) {
version = json_object_get_string_member (json_obj, "RedfishVersion");
}
if (json_object_has_member (json_obj, "UUID"))
uuid = json_object_get_string_member (json_obj, "UUID");
g_debug ("Version: %s", version);
g_debug ("UUID: %s", uuid);
if (json_object_has_member (json_obj, "UpdateService"))
json_update_service = json_object_get_object_member (json_obj, "UpdateService");
if (json_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 (json_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;
}
void
fu_redfish_backend_set_hostname (FuRedfishBackend *self, const gchar *hostname)
{
g_free (self->hostname);
self->hostname = g_strdup (hostname);
}
void
fu_redfish_backend_set_port (FuRedfishBackend *self, guint port)
{
self->port = port;
}
void
fu_redfish_backend_set_https (FuRedfishBackend *self, gboolean use_https)
{
self->use_https = use_https;
}
void
fu_redfish_backend_set_cacheck (FuRedfishBackend *self, gboolean cacheck)
{
self->cacheck = cacheck;
}
void
fu_redfish_backend_set_wildcard_targets (FuRedfishBackend *self, gboolean wildcard_targets)
{
self->wildcard_targets = wildcard_targets;
}
void
fu_redfish_backend_set_username (FuRedfishBackend *self, const gchar *username)
{
g_free (self->username);
self->username = g_strdup (username);
}
void
fu_redfish_backend_set_password (FuRedfishBackend *self, const gchar *password)
{
g_free (self->password);
self->password = g_strdup (password);
}
const gchar *
fu_redfish_backend_get_push_uri_path (FuRedfishBackend *self)
{
return self->push_uri_path;
}
static void
fu_redfish_backend_finalize (GObject *object)
{
FuRedfishBackend *self = FU_REDFISH_BACKEND (object);
g_hash_table_unref (self->request_cache);
curl_share_cleanup (self->curlsh);
g_free (self->update_uri_path);
g_free (self->push_uri_path);
g_free (self->hostname);
g_free (self->username);
g_free (self->password);
G_OBJECT_CLASS (fu_redfish_backend_parent_class)->finalize (object);
}
static void
fu_redfish_backend_class_init (FuRedfishBackendClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FuBackendClass *klass_backend = FU_BACKEND_CLASS (klass);
klass_backend->coldplug = fu_redfish_backend_coldplug;
klass_backend->setup = fu_redfish_backend_setup;
object_class->finalize = fu_redfish_backend_finalize;
}
static void
fu_redfish_backend_init (FuRedfishBackend *self)
{
self->use_https = TRUE;
self->device_gtype = FU_TYPE_REDFISH_DEVICE;
self->request_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) g_byte_array_unref);
self->curlsh = curl_share_init ();
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
}
FuRedfishBackend *
fu_redfish_backend_new (FuContext *ctx)
{
return FU_REDFISH_BACKEND (g_object_new (FU_REDFISH_TYPE_BACKEND,
"name", "redfish",
"context", ctx,
NULL));
}