/*
* 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);
}