mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-25 22:40:50 +00:00

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.
440 lines
13 KiB
C
440 lines
13 KiB
C
/*
|
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-redfish-backend.h"
|
|
#include "fu-redfish-common.h"
|
|
#include "fu-redfish-legacy-device.h"
|
|
#include "fu-redfish-multipart-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);
|
|
}
|
|
|
|
const gchar *
|
|
fu_redfish_backend_get_username(FuRedfishBackend *self)
|
|
{
|
|
return self->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));
|
|
}
|