/* * ovirt-proxy.c * * Copyright (C) 2011, 2013 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * * Author: Christophe Fergeau */ #undef OVIRT_DEBUG #include #include "ovirt-error.h" #include "ovirt-rest-call-error.h" #include "ovirt-proxy.h" #include "ovirt-proxy-private.h" #include "ovirt-rest-call.h" #include "ovirt-vm.h" #include "ovirt-vm-display.h" #include "govirt-private.h" #include #include #include #include #include #include #include G_DEFINE_TYPE (OvirtProxy, ovirt_proxy, REST_TYPE_PROXY); #define OVIRT_PROXY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), OVIRT_TYPE_PROXY, OvirtProxyPrivate)) enum { PROP_0, PROP_CA_CERT, PROP_ADMIN, PROP_SESSION_ID, PROP_SSO_TOKEN }; #define CA_CERT_FILENAME "ca.crt" static gboolean set_ca_cert_from_data(OvirtProxy *proxy, char *ca_cert_data, gsize ca_cert_len); static GByteArray *get_ca_cert_data(OvirtProxy *proxy); static void ovirt_proxy_set_tmp_ca_file(OvirtProxy *proxy, const char *ca_file); #ifdef OVIRT_DEBUG static void dump_display(OvirtVmDisplay *display) { OvirtVmDisplayType type; guint monitor_count; gchar *address; guint port; guint secure_port; gchar *ticket; guint expiry; g_object_get(G_OBJECT(display), "type", &type, "monitor-count", &monitor_count, "address", &address, "port", &port, "secure-port", &secure_port, "ticket", &ticket, "expiry", &expiry, NULL); g_print("\tDisplay:\n"); g_print("\t\tType: %s\n", (type == OVIRT_VM_DISPLAY_VNC)?"vnc":"spice"); g_print("\t\tMonitors: %d\n", monitor_count); g_print("\t\tAddress: %s\n", address); g_print("\t\tPort: %d\n", port); g_print("\t\tSecure Port: %d\n", secure_port); g_print("\t\tTicket: %s\n", ticket); g_print("\t\tExpiry: %d\n", expiry); g_free(address); g_free(ticket); } static void dump_key(gpointer key, gpointer value, gpointer user_data) { g_print("[%s] -> %p\n", (char *)key, value); } static void dump_action(gpointer key, gpointer value, gpointer user_data) { g_print("\t\t%s -> %s\n", (char *)key, (char *)value); } static void dump_vm(OvirtVm *vm) { gchar *name; gchar *uuid; gchar *href; OvirtVmState state; GHashTable *actions = NULL; OvirtVmDisplay *display; g_object_get(G_OBJECT(vm), "name", &name, "uuid", &uuid, "href", &href, "state", &state, "display", &display, NULL); g_print("VM:\n"); g_print("\tName: %s\n", name); g_print("\tuuid: %s\n", uuid); g_print("\thref: %s\n", href); g_print("\tState: %s\n", (state == OVIRT_VM_STATE_UP)?"up":"down"); if (actions != NULL) { g_print("\tActions:\n"); g_hash_table_foreach(actions, dump_action, NULL); g_hash_table_unref(actions); } if (display != NULL) { dump_display(display); g_object_unref(display); } g_free(name); g_free(uuid); g_free(href); } #endif static RestProxyCall *ovirt_rest_call_new(OvirtProxy *proxy, const char *method, const char *href) { RestProxyCall *call; g_return_val_if_fail(OVIRT_IS_PROXY(proxy), NULL); call = REST_PROXY_CALL(ovirt_action_rest_call_new(REST_PROXY(proxy))); if (method != NULL) { rest_proxy_call_set_method(call, method); } rest_proxy_call_set_function(call, href); /* FIXME: to set or not to set ?? */ rest_proxy_call_add_header(call, "All-Content", "true"); return call; } RestXmlNode *ovirt_proxy_get_collection_xml(OvirtProxy *proxy, const char *href, GError **error) { RestProxyCall *call; RestXmlNode *root; GError *err = NULL; g_return_val_if_fail(OVIRT_IS_PROXY(proxy), NULL); call = ovirt_rest_call_new(proxy, "GET", href); if (!rest_proxy_call_sync(call, &err)) { if (g_error_matches(err, REST_PROXY_ERROR, REST_PROXY_ERROR_CANCELLED)) { g_set_error_literal(error, OVIRT_REST_CALL_ERROR, OVIRT_REST_CALL_ERROR_CANCELLED, err->message); g_clear_error(&err); } else if (err != NULL) { g_warning("Error while getting collection: %s", err->message); g_propagate_error(error, err); } else { g_warning("Error while getting collection"); } g_object_unref(G_OBJECT(call)); return NULL; } root = ovirt_rest_xml_node_from_call(call); g_object_unref(G_OBJECT(call)); return root; } typedef struct { OvirtProxy *proxy; GSimpleAsyncResult *result; GCancellable *cancellable; gulong cancellable_cb_id; OvirtProxyCallAsyncCb call_async_cb; gpointer call_user_data; GDestroyNotify destroy_call_data; } OvirtProxyCallAsyncData; static void ovirt_proxy_call_async_data_free(OvirtProxyCallAsyncData *data) { if (data->destroy_call_data != NULL) { data->destroy_call_data(data->call_user_data); } if (data->proxy != NULL) { g_object_unref(G_OBJECT(data->proxy)); } if (data->result != NULL) { g_object_unref(G_OBJECT(data->result)); } if ((data->cancellable != NULL) && (data->cancellable_cb_id != 0)) { g_cancellable_disconnect(data->cancellable, data->cancellable_cb_id); } g_slice_free(OvirtProxyCallAsyncData, data); } static void call_async_cancelled_cb (G_GNUC_UNUSED GCancellable *cancellable, RestProxyCall *call) { rest_proxy_call_cancel(call); } static void call_async_cb(RestProxyCall *call, const GError *error, G_GNUC_UNUSED GObject *weak_object, gpointer user_data) { OvirtProxyCallAsyncData *data = user_data; GSimpleAsyncResult *result = data->result; if (error != NULL) { g_simple_async_result_set_from_error(result, error); } else { GError *call_error = NULL; gboolean callback_result = TRUE; if (data->call_async_cb != NULL) { callback_result = data->call_async_cb(data->proxy, call, data->call_user_data, &call_error); if (call_error != NULL) { g_simple_async_result_set_from_error(result, call_error); } } g_simple_async_result_set_op_res_gboolean(result, callback_result); } g_simple_async_result_complete (result); ovirt_proxy_call_async_data_free(data); } void ovirt_rest_call_async(OvirtRestCall *call, GSimpleAsyncResult *result, GCancellable *cancellable, OvirtProxyCallAsyncCb callback, gpointer user_data, GDestroyNotify destroy_func) { OvirtProxy *proxy; GError *error = NULL; OvirtProxyCallAsyncData *data; g_return_if_fail((cancellable == NULL) || G_IS_CANCELLABLE(cancellable)); g_object_get(G_OBJECT(call), "proxy", &proxy, NULL); g_return_if_fail(OVIRT_IS_PROXY(proxy)); data = g_slice_new0(OvirtProxyCallAsyncData); data->proxy = proxy; data->result = result; data->call_async_cb = callback; data->call_user_data = user_data; data->destroy_call_data = destroy_func; if (cancellable != NULL) { data->cancellable = cancellable; data->cancellable_cb_id = g_cancellable_connect(cancellable, G_CALLBACK (call_async_cancelled_cb), call, NULL); } if (!rest_proxy_call_async(REST_PROXY_CALL(call), call_async_cb, NULL, data, &error)) { g_warning("Error while getting collection XML"); g_simple_async_result_set_from_error(result, error); g_simple_async_result_complete(result); ovirt_proxy_call_async_data_free(data); } } gboolean ovirt_rest_call_finish(GAsyncResult *result, GError **err) { GSimpleAsyncResult *simple; simple = G_SIMPLE_ASYNC_RESULT(result); if (g_simple_async_result_propagate_error(simple, err)) return FALSE; return g_simple_async_result_get_op_res_gboolean(simple); } typedef struct { OvirtProxyGetCollectionAsyncCb parser; gpointer user_data; GDestroyNotify destroy_user_data; } OvirtProxyGetCollectionAsyncData; static void ovirt_proxy_get_collection_async_data_destroy(OvirtProxyGetCollectionAsyncData *data) { if (data->destroy_user_data != NULL) { data->destroy_user_data(data->user_data); } g_slice_free(OvirtProxyGetCollectionAsyncData, data); } static gboolean get_collection_xml_async_cb(OvirtProxy* proxy, RestProxyCall *call, gpointer user_data, GError **error) { RestXmlNode *root; OvirtProxyGetCollectionAsyncData *data; gboolean parsed = FALSE; data = (OvirtProxyGetCollectionAsyncData *)user_data; root = ovirt_rest_xml_node_from_call(call); /* Do the parsing */ g_warn_if_fail(data->parser != NULL); if (data->parser != NULL) { parsed = data->parser(proxy, root, data->user_data, error); } rest_xml_node_unref(root); return parsed; } /** * ovirt_proxy_get_collection_xml_async: * @proxy: a #OvirtProxy * @callback: (scope async): completion callback * @user_data: (closure): opaque data for callback */ void ovirt_proxy_get_collection_xml_async(OvirtProxy *proxy, const char *href, GSimpleAsyncResult *result, GCancellable *cancellable, OvirtProxyGetCollectionAsyncCb callback, gpointer user_data, GDestroyNotify destroy_func) { OvirtProxyGetCollectionAsyncData *data; RestProxyCall *call; data = g_slice_new0(OvirtProxyGetCollectionAsyncData); data->parser = callback; data->user_data = user_data; data->destroy_user_data = destroy_func; call = ovirt_rest_call_new(proxy, "GET", href); ovirt_rest_call_async(OVIRT_REST_CALL(call), result, cancellable, get_collection_xml_async_cb, data, (GDestroyNotify)ovirt_proxy_get_collection_async_data_destroy); g_object_unref(call); } static GFile *get_ca_cert_file(OvirtProxy *proxy) { gchar *base_uri = NULL; gchar *ca_uri = NULL; GFile *ca_file = NULL; g_object_get(G_OBJECT(proxy), "url-format", &base_uri, NULL); if (base_uri == NULL) goto error; ca_uri = g_build_filename(base_uri, CA_CERT_FILENAME, NULL); g_debug("CA certificate URI: %s", ca_uri); ca_file = g_file_new_for_uri(ca_uri); error: g_free(base_uri); g_free(ca_uri); return ca_file; } static GByteArray *get_ca_cert_data(OvirtProxy *proxy) { char *ca_file = NULL; char *content; gsize length; GError *error = NULL; gboolean read_success; g_object_get(G_OBJECT(proxy), "ssl-ca-file", &ca_file, NULL); if (ca_file == NULL) { return NULL; } read_success = g_file_get_contents(ca_file, &content, &length, &error); if (!read_success) { if (error != NULL) { g_warning("Couldn't read %s: %s", ca_file, error->message); } else { g_warning("Couldn't read %s", ca_file); } g_free(ca_file); return NULL; } g_free(ca_file); return g_byte_array_new_take((guchar *)content, length); } static void ovirt_proxy_free_tmp_ca_file(OvirtProxy *proxy) { if (proxy->priv->tmp_ca_file != NULL) { int unlink_failed; unlink_failed = g_unlink(proxy->priv->tmp_ca_file); if (unlink_failed == -1) { g_warning("Failed to unlink '%s'", proxy->priv->tmp_ca_file); } g_free(proxy->priv->tmp_ca_file); proxy->priv->tmp_ca_file = NULL; } } static void ovirt_proxy_set_tmp_ca_file(OvirtProxy *proxy, const char *ca_file) { ovirt_proxy_free_tmp_ca_file(proxy); proxy->priv->tmp_ca_file = g_strdup(ca_file); if (ca_file != NULL) { /* We block invokations of ssl_ca_file_changed() using the 'setting_ca_file' boolean * g_signal_handler_{un,}block is not working well enough as * ovirt_proxy_set_tmp_ca_file() can be called as part of a g_object_set call, * and unblocking "notify::ssl-ca-file" right after setting its value * is not enough to prevent ssl_ca_file_changed() from running. */ proxy->priv->setting_ca_file = TRUE; g_object_set(G_OBJECT(proxy), "ssl-ca-file", ca_file, NULL); } } #if GLIB_CHECK_VERSION(2, 32, 0) static char *write_to_tmp_file(const char *template, const char *data, gsize data_len, GError **error) { GFile *tmp_file = NULL; GFileIOStream *iostream = NULL; GOutputStream *output; gboolean write_ok; char *result = NULL; tmp_file = g_file_new_tmp(template, &iostream, error); if (tmp_file == NULL) { goto end; } output = g_io_stream_get_output_stream(G_IO_STREAM(iostream)); g_return_val_if_fail(output != NULL, FALSE); write_ok = g_output_stream_write_all(output, data, data_len, NULL, NULL, error); if (!write_ok) { goto end; } result = g_file_get_path(tmp_file); end: if (tmp_file != NULL) { g_object_unref(G_OBJECT(tmp_file)); } if (iostream != NULL) { g_object_unref(G_OBJECT(iostream)); } return result; } #else static char *write_to_tmp_file(const char *template, const char *data, gsize data_len, GError **error) { int fd = -1; char *tmp_file = NULL; char *result = NULL; fd = g_file_open_tmp(template, &tmp_file, error); if (fd == -1) { goto end; } while (data_len != 0) { ssize_t bytes_written; bytes_written = write(fd, data, data_len); if (bytes_written == -1) { if ((errno != EINTR) || (errno != EAGAIN)) { g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errno), _("Failed to write to '%s': %s"), tmp_file, strerror(errno)); goto end; } } g_assert(bytes_written <= (ssize_t)data_len); data_len -= bytes_written; } result = tmp_file; tmp_file = NULL; end: if (fd != -1) { int close_status = close(fd); if (close_status != 0) { g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errno), _("Failed to close '%s': %s"), result, strerror(errno)); g_free(result); result = NULL; } } g_free(tmp_file); return result; } #endif static gboolean set_ca_cert_from_data(OvirtProxy *proxy, char *ca_cert_data, gsize ca_cert_len) { /* This function is quite complicated for historical reasons, we * initially only had a ca-cert property in OvirtProxy with type * GByteArray. RestProxy then got a ssl-ca-file property storing a * filename. We want to use ssl-ca-file as the canonical property, * and set ca-cert value from it. However, when the user sets * the ca-cert property, we need to create a temporary file in order * to be able to keep the ssl-ca-file property synced with it */ char *ca_file_path = NULL; GError *error = NULL; gboolean result = FALSE; if (ca_cert_data != NULL) { ca_file_path = write_to_tmp_file("govirt-ca-XXXXXX.crt", ca_cert_data, ca_cert_len, &error); if (ca_file_path == NULL) { g_warning("Failed to create temporary file for CA certificate: %s", error->message); goto end; } } else { ca_file_path = NULL; } ovirt_proxy_set_tmp_ca_file(proxy, ca_file_path); g_free(ca_file_path); g_object_notify(G_OBJECT(proxy), "ca-cert"); result = TRUE; end: g_clear_error(&error); return result; } static void ovirt_proxy_update_vm_display_ca(OvirtProxy *proxy) { GList *vms; GList *it; vms = ovirt_proxy_get_vms_internal(proxy); for (it = vms; it != NULL; it = it->next) { OvirtVm *vm = OVIRT_VM(it->data); OvirtVmDisplay *display; g_object_get(G_OBJECT(vm), "display", &display, NULL); if (display != NULL) { g_object_set(G_OBJECT(display), "ca-cert", proxy->priv->display_ca, NULL); g_object_unref(display); } else { char *name; g_object_get(vm, "name", &name, NULL); g_debug("Not setting display CA for '%s' since it has no display", name); g_free(name); } } g_list_free(vms); } static void set_display_ca_cert_from_data(OvirtProxy *proxy, char *ca_cert_data, gsize ca_cert_len) { if (proxy->priv->display_ca != NULL) g_byte_array_unref(proxy->priv->display_ca); proxy->priv->display_ca = g_byte_array_new_take((guint8 *)ca_cert_data, ca_cert_len); /* While the fetched CA certificate has historically been used both as the CA * certificate used during REST API communication and as the one to use for * SPICE communication, this function really fetches the one meant for SPICE * communication. The one used for REST API communication may or may not be * the same. * An OvirtVmDisplay::ca-cert property has been added when this design issue * became apparent, so we need to update this property after fetching the * certificate. */ ovirt_proxy_update_vm_display_ca(proxy); } gboolean ovirt_proxy_fetch_ca_certificate(OvirtProxy *proxy, GError **error) { GFile *source = NULL; char *cert_data; gsize cert_length; gboolean load_ok = FALSE; g_return_val_if_fail(OVIRT_IS_PROXY(proxy), FALSE); g_return_val_if_fail((error == NULL) || (*error == NULL), FALSE); source = get_ca_cert_file(proxy); if (source == NULL) { g_set_error(error, OVIRT_ERROR, OVIRT_ERROR_BAD_URI, _("Could not extract CA certificate filename from URI")); goto error; } load_ok = g_file_load_contents(source, NULL, &cert_data, &cert_length, NULL, error); if (!load_ok) goto error; set_ca_cert_from_data(proxy, cert_data, cert_length); /* takes ownership of cert_data */ set_display_ca_cert_from_data(proxy, cert_data, cert_length); error: if (source != NULL) g_object_unref(source); return load_ok; } static void ca_file_loaded_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { GSimpleAsyncResult *fetch_result; GObject *proxy; GError *error = NULL; char *cert_data; gsize cert_length; fetch_result = G_SIMPLE_ASYNC_RESULT(user_data); g_file_load_contents_finish(G_FILE(source_object), res, &cert_data, &cert_length, NULL, &error); if (error != NULL) { g_simple_async_result_take_error(fetch_result, error); goto end; } proxy = g_async_result_get_source_object(G_ASYNC_RESULT(fetch_result)); set_ca_cert_from_data(OVIRT_PROXY(proxy), cert_data, cert_length); /* takes ownership of cert_data */ set_display_ca_cert_from_data(OVIRT_PROXY(proxy), cert_data, cert_length); g_object_unref(proxy); g_simple_async_result_set_op_res_gboolean(fetch_result, TRUE); end: g_simple_async_result_complete (fetch_result); g_object_unref(fetch_result); } void ovirt_proxy_fetch_ca_certificate_async(OvirtProxy *proxy, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GFile *ca_file; GSimpleAsyncResult *result; g_return_if_fail(OVIRT_IS_PROXY(proxy)); g_return_if_fail((cancellable == NULL) || G_IS_CANCELLABLE(cancellable)); ca_file = get_ca_cert_file(proxy); g_return_if_fail(ca_file != NULL); result = g_simple_async_result_new(G_OBJECT(proxy), callback, user_data, ovirt_proxy_fetch_ca_certificate_async); g_file_load_contents_async(ca_file, cancellable, ca_file_loaded_cb, result); g_object_unref(ca_file); } /** * ovirt_proxy_fetch_ca_certificate_finish: * * Return value: (transfer full): */ GByteArray *ovirt_proxy_fetch_ca_certificate_finish(OvirtProxy *proxy, GAsyncResult *result, GError **err) { g_return_val_if_fail(OVIRT_IS_PROXY(proxy), NULL); g_return_val_if_fail(g_simple_async_result_is_valid(result, G_OBJECT(proxy), ovirt_proxy_fetch_ca_certificate_async), NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); if (g_simple_async_result_propagate_error(G_SIMPLE_ASYNC_RESULT(result), err)) return NULL; return get_ca_cert_data(proxy); } static void ovirt_proxy_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { OvirtProxy *proxy = OVIRT_PROXY(object); switch (prop_id) { case PROP_CA_CERT: g_value_take_boxed(value, get_ca_cert_data(proxy)); break; case PROP_ADMIN: g_value_set_boolean(value, proxy->priv->admin_mode); break; case PROP_SESSION_ID: g_value_set_string(value, proxy->priv->jsessionid); break; case PROP_SSO_TOKEN: g_value_set_string(value, proxy->priv->sso_token); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } } static void ovirt_proxy_set_session_id(OvirtProxy *proxy, const char *session_id) { char *url; char *domain; g_object_get(G_OBJECT(proxy), "url-format", &url, NULL); g_return_if_fail(url != NULL); if (g_str_has_prefix(url, "https://")) { domain = url + strlen("https://"); } else { domain = url; } if (proxy->priv->jsessionid_cookie != NULL) { soup_cookie_jar_delete_cookie(proxy->priv->cookie_jar, proxy->priv->jsessionid_cookie); proxy->priv->jsessionid_cookie = NULL; } g_free(proxy->priv->jsessionid); proxy->priv->jsessionid = g_strdup(session_id); if (proxy->priv->jsessionid != NULL) { SoupCookie *cookie; cookie = soup_cookie_new("JSESSIONID", session_id, domain, "/ovirt-engine/api", -1); soup_cookie_jar_add_cookie(proxy->priv->cookie_jar, cookie); proxy->priv->jsessionid_cookie = cookie; ovirt_proxy_add_header(proxy, "Prefer", "persistent-auth"); } else { ovirt_proxy_add_header(proxy, "Prefer", NULL); } g_free(url); } static void ovirt_proxy_set_sso_token(OvirtProxy *proxy, const char *sso_token) { char *header_value; g_free(proxy->priv->sso_token); proxy->priv->sso_token = g_strdup(sso_token); header_value = g_strdup_printf("Bearer %s", sso_token); ovirt_proxy_add_header(proxy, "Authorization", header_value); g_free(header_value); } static void ovirt_proxy_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { OvirtProxy *proxy = OVIRT_PROXY(object); switch (prop_id) { case PROP_CA_CERT: { GByteArray *ca_cert; ca_cert = g_value_get_boxed(value); if (ca_cert != NULL) { set_ca_cert_from_data(proxy, (char *)ca_cert->data, ca_cert->len); } else { set_ca_cert_from_data(proxy, NULL, 0); } break; } case PROP_ADMIN: proxy->priv->admin_mode = g_value_get_boolean(value); break; case PROP_SESSION_ID: ovirt_proxy_set_session_id(proxy, g_value_get_string(value)); break; case PROP_SSO_TOKEN: ovirt_proxy_set_sso_token(proxy, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } } static void ovirt_proxy_dispose(GObject *obj) { OvirtProxy *proxy = OVIRT_PROXY(obj); if (proxy->priv->cookie_jar) { g_object_unref(G_OBJECT(proxy->priv->cookie_jar)); proxy->priv->cookie_jar = NULL; } g_warn_if_fail(proxy->priv->additional_headers != NULL); g_hash_table_unref(proxy->priv->additional_headers); proxy->priv->additional_headers = NULL; if (proxy->priv->api != NULL) { g_object_unref(proxy->priv->api); proxy->priv->api = NULL; } if (proxy->priv->display_ca != NULL) { g_byte_array_unref(proxy->priv->display_ca); proxy->priv->display_ca = NULL; } G_OBJECT_CLASS(ovirt_proxy_parent_class)->dispose(obj); } static void ovirt_proxy_finalize(GObject *obj) { OvirtProxy *proxy = OVIRT_PROXY(obj); ovirt_proxy_free_tmp_ca_file(proxy); g_free(proxy->priv->jsessionid); g_free(proxy->priv->sso_token); G_OBJECT_CLASS(ovirt_proxy_parent_class)->finalize(obj); } static void ovirt_proxy_constructed(GObject *gobject) { if (g_getenv("GOVIRT_NO_SSL_STRICT") != NULL) { g_warning("Disabling strict checking of SSL certificates"); g_object_set(OVIRT_PROXY(gobject), "ssl-strict", FALSE, NULL); } /* Chain up to the parent class */ if (G_OBJECT_CLASS(ovirt_proxy_parent_class)->constructed) G_OBJECT_CLASS(ovirt_proxy_parent_class)->constructed(gobject); } static void ovirt_proxy_class_init(OvirtProxyClass *klass) { GObjectClass *oclass = G_OBJECT_CLASS(klass); oclass->dispose = ovirt_proxy_dispose; oclass->finalize = ovirt_proxy_finalize; oclass->constructed = ovirt_proxy_constructed; oclass->get_property = ovirt_proxy_get_property; oclass->set_property = ovirt_proxy_set_property; /** * OvirtProxy:ca-cert: * * Path to a file containing the CA certificates to use for the HTTPS * REST API communication with the oVirt instance */ g_object_class_install_property(oclass, PROP_CA_CERT, g_param_spec_boxed("ca-cert", "ca-cert", "Virt CA certificate to use for HTTPS REST communication", G_TYPE_BYTE_ARRAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * OvirtProxy:admin: * * Indicates whether to connect to the REST API as an admin, or as a regular user. * Different content will be shown for the same user depending on if they connect as * an admin or not. Connecting as an admin requires to have admin priviledges on the * oVirt instance. * * Since: 0.0.2 */ g_object_class_install_property(oclass, PROP_ADMIN, g_param_spec_boolean("admin", "admin", "Use REST API as an admin", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * OvirtProxy:session-id: * * jsessionid cookie value. This allows to use the REST API without * authenticating first. This was used by oVirt 3.6 and is now replaced * by OvirtProxy:sso-token. * * Since: 0.3.1 */ g_object_class_install_property(oclass, PROP_SESSION_ID, g_param_spec_string("session-id", "session-id", "oVirt/RHEV JSESSIONID", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * OvirtProxy:sso-token: * * Token to use for SSO. This allows to use the REST API without * authenticating first. This is used starting with oVirt 4.0. * * Since: 0.3.4 */ g_object_class_install_property(oclass, PROP_SSO_TOKEN, g_param_spec_string("sso-token", "sso-token", "oVirt/RHEV SSO token", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_type_class_add_private(klass, sizeof(OvirtProxyPrivate)); } static void ssl_ca_file_changed(GObject *gobject, GParamSpec *pspec, gpointer user_data) { OvirtProxy *proxy = OVIRT_PROXY(gobject); if (proxy->priv->setting_ca_file) { proxy->priv->setting_ca_file = FALSE; return; } ovirt_proxy_free_tmp_ca_file(OVIRT_PROXY(gobject)); } static void ovirt_proxy_init(OvirtProxy *self) { gulong handler_id; self->priv = OVIRT_PROXY_GET_PRIVATE(self); handler_id = g_signal_connect(G_OBJECT(self), "notify::ssl-ca-file", (GCallback)ssl_ca_file_changed, NULL); self->priv->ssl_ca_file_changed_id = handler_id; self->priv->cookie_jar = soup_cookie_jar_new(); rest_proxy_add_soup_feature(REST_PROXY(self), SOUP_SESSION_FEATURE(self->priv->cookie_jar)); self->priv->additional_headers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } /* FIXME : "uri" should just be a base domain, foo.example.com/some/path * govirt will then prepend https:// * /api/ is part of what librest call 'function' * -> get rid of all the /api/ stripping here and there */ OvirtProxy *ovirt_proxy_new(const char *hostname) { char *uri; OvirtProxy *proxy; gsize suffix_len = 0; int i; if (!g_str_has_prefix(hostname, "http://") && !g_str_has_prefix(hostname, "https://")) { if (g_getenv("GOVIRT_DISABLE_HTTPS") != NULL) { g_warning("Using plain text HTTP connection"); uri = g_strconcat("http://", hostname, NULL); } else { uri = g_strconcat("https://", hostname, NULL); } } else { /* Fallback code for backwards API compat, early libgovirt versions * expected a full fledged URI */ g_warning("Passing a full http:// or https:// URI to " "ovirt_proxy_new() is deprecated"); uri = g_strdup(hostname); } /* More backwards compat code, strip "/api" from URI */ if (g_str_has_suffix(uri, "api")) { suffix_len = strlen("api"); } else if (g_str_has_suffix(uri, "/api")) { suffix_len = strlen("/api"); } else if (g_str_has_suffix(uri, "/api/")) { suffix_len = strlen("/api/"); } if (suffix_len != 0) { g_warning("Passing an URI ending in /api to ovirt_proxy_new() " "is deprecated"); uri[strlen(uri) - suffix_len] = '\0'; } /* Strip trailing '/' */ for (i = strlen(uri)-1; i >= 0; i--) { if (uri[i] != '/') { break; } uri[i] = '\0'; } /* We disable cookies upon OvirtProxy creation as we will be * adding our own cookie jar to OvirtProxy */ proxy = OVIRT_PROXY(g_object_new(OVIRT_TYPE_PROXY, "url-format", uri, "disable-cookies", TRUE, NULL)); g_free(uri); return proxy; } static void vm_collection_changed(GObject *gobject, GParamSpec *pspec, gpointer user_data) { ovirt_proxy_update_vm_display_ca(OVIRT_PROXY(user_data)); } /** * ovirt_proxy_add_header: * @proxy: a #OvirtProxy * @header: name of the header to add to each request * @value: value of the header to add to each request * * Add a http header called @header with the value @value to each oVirt REST * API call. If a header with this name already exists, the new value will * replace the old. If @value is NULL then the header will be removed. */ void ovirt_proxy_add_header(OvirtProxy *proxy, const char *header, const char *value) { g_return_if_fail(OVIRT_IS_PROXY(proxy)); if (value != NULL) { g_hash_table_replace(proxy->priv->additional_headers, g_strdup(header), g_strdup(value)); } else { g_hash_table_remove(proxy->priv->additional_headers, header); } } /** * ovirt_proxy_add_headers: * @proxy: a #OvirtProxy * @...: header name and value pairs, followed by %NULL * * Add the specified http header and value pairs to @proxy. These headers will * be sent with each oVirt REST API call. If a header already exists, the new * value will replace the old. */ void ovirt_proxy_add_headers(OvirtProxy *proxy, ...) { va_list headers; g_return_if_fail(OVIRT_IS_PROXY(proxy)); va_start(headers, proxy); ovirt_proxy_add_headers_from_valist(proxy, headers); va_end(headers); } /** * ovirt_proxy_add_headers_from_valist: * @proxy: a #OvirtProxy * @headers: header name and value pairs * * Add the specified http header and value pairs to @proxy. These headers will * be sent with each oVirt REST API call. If a header already exists, the new * value will replace the old. */ void ovirt_proxy_add_headers_from_valist(OvirtProxy *proxy, va_list headers) { const char *header = NULL; const char *value; g_return_if_fail(OVIRT_IS_PROXY(proxy)); header = va_arg(headers, const char *); while (header != NULL) { value = va_arg(headers, const char *); ovirt_proxy_add_header(proxy, header, value); header = va_arg(headers, const char *); } } void ovirt_proxy_append_additional_headers(OvirtProxy *proxy, RestProxyCall *call) { GHashTableIter iter; gpointer key; gpointer value; g_return_if_fail(OVIRT_IS_PROXY(proxy)); g_return_if_fail(REST_IS_PROXY_CALL(call)); g_hash_table_iter_init(&iter, proxy->priv->additional_headers); while (g_hash_table_iter_next (&iter, &key, &value)) { rest_proxy_call_add_header(call, key, value); } } static void ovirt_proxy_set_api_from_xml(OvirtProxy *proxy, RestXmlNode *node, GError **error) { OvirtCollection *vms; if (proxy->priv->api != NULL) { g_object_unref(G_OBJECT(proxy->priv->api)); } proxy->priv->api = ovirt_api_new_from_xml(node, error); vms = ovirt_api_get_vms(proxy->priv->api); g_return_if_fail(vms != NULL); g_signal_connect(G_OBJECT(vms), "notify::resources", (GCallback)vm_collection_changed, proxy); } /** * ovirt_proxy_fetch_api: * @proxy: a #OvirtProxy * @error: #GError to set on error, or NULL * * Return value: (transfer none): */ OvirtApi *ovirt_proxy_fetch_api(OvirtProxy *proxy, GError **error) { RestXmlNode *api_node; g_return_val_if_fail(OVIRT_IS_PROXY(proxy), FALSE); api_node = ovirt_proxy_get_collection_xml(proxy, "/ovirt-engine/api", error); if (api_node == NULL) { return NULL; } ovirt_proxy_set_api_from_xml(proxy, api_node, error); rest_xml_node_unref(api_node); return proxy->priv->api; } static gboolean fetch_api_async_cb(OvirtProxy* proxy, RestXmlNode *root_node, gpointer user_data, GError **error) { ovirt_proxy_set_api_from_xml(proxy, root_node, error); return TRUE; } /** * ovirt_proxy_fetch_api_async: * @proxy: a #OvirtProxy * @callback: (scope async): completion callback * @user_data: (closure): opaque data for callback */ void ovirt_proxy_fetch_api_async(OvirtProxy *proxy, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail(OVIRT_IS_PROXY(proxy)); g_return_if_fail((cancellable == NULL) || G_IS_CANCELLABLE(cancellable)); result = g_simple_async_result_new (G_OBJECT(proxy), callback, user_data, ovirt_proxy_fetch_api_async); ovirt_proxy_get_collection_xml_async(proxy, "/ovirt-engine/api", result, cancellable, fetch_api_async_cb, NULL, NULL); } /** * ovirt_proxy_fetch_api_finish: * @proxy: a #OvirtProxy * @result: (transfer none): async method result * * Return value: (transfer none): an #OvirtApi instance to interact with * oVirt/RHEV REST API. */ OvirtApi * ovirt_proxy_fetch_api_finish(OvirtProxy *proxy, GAsyncResult *result, GError **err) { g_return_val_if_fail(OVIRT_IS_PROXY(proxy), NULL); g_return_val_if_fail(g_simple_async_result_is_valid(result, G_OBJECT(proxy), ovirt_proxy_fetch_api_async), NULL); if (g_simple_async_result_propagate_error(G_SIMPLE_ASYNC_RESULT(result), err)) return NULL; return proxy->priv->api; } /** * ovirt_proxy_get_api: * * Gets the api entry point to access remote oVirt resources and collections. * This method does not initiate any network activity, the remote API entry point * must have been fetched with ovirt_proxy_fetch_api() or * ovirt_proxy_fetch_api_async() before calling this function. * * Return value: (transfer none): an #OvirtApi instance used to interact with * oVirt REST API. */ OvirtApi * ovirt_proxy_get_api(OvirtProxy *proxy) { return proxy->priv->api; } GList *ovirt_proxy_get_vms_internal(OvirtProxy *proxy) { OvirtApi *api; OvirtCollection *vm_collection; GHashTable *vms; g_return_val_if_fail(OVIRT_IS_PROXY(proxy), NULL); api = ovirt_proxy_get_api(proxy); if (api == NULL) return NULL; vm_collection = ovirt_api_get_vms(api); if (vm_collection == NULL) return NULL; vms = ovirt_collection_get_resources(vm_collection); if (vms == NULL) return NULL; return g_hash_table_get_values(vms); }