/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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)); }