aboutsummaryrefslogtreecommitdiff
path: root/src/uccs-server.c
diff options
context:
space:
mode:
authorMike Gabriel <mike.gabriel@das-netzwerkteam.de>2014-11-02 20:44:45 +0100
committerMike Gabriel <mike.gabriel@das-netzwerkteam.de>2014-11-02 20:44:45 +0100
commitfdc39509763f7d60429b903474916684da6653eb (patch)
treea44e03a9935392ea693088f25a37332cfffa9cb6 /src/uccs-server.c
downloadremote-logon-service-fdc39509763f7d60429b903474916684da6653eb.tar.gz
remote-logon-service-fdc39509763f7d60429b903474916684da6653eb.tar.bz2
remote-logon-service-fdc39509763f7d60429b903474916684da6653eb.zip
Imported Upstream version 1.0.0upstream/1.0.0
Diffstat (limited to 'src/uccs-server.c')
-rw-r--r--src/uccs-server.c949
1 files changed, 949 insertions, 0 deletions
diff --git a/src/uccs-server.c b/src/uccs-server.c
new file mode 100644
index 0000000..c17cf38
--- /dev/null
+++ b/src/uccs-server.c
@@ -0,0 +1,949 @@
+/*
+ * Copyright © 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ted Gould <ted@canonical.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+
+#include <json-glib/json-glib.h>
+
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "uccs-server.h"
+#include "defines.h"
+
+#include "rdp-server.h"
+#include "citrix-server.h"
+
+#include "crypt.h"
+
+static void uccs_server_class_init (UccsServerClass *klass);
+static void uccs_server_init (UccsServer *self);
+static void uccs_server_dispose (GObject *object);
+static void uccs_server_finalize (GObject *object);
+static GVariant * get_properties (Server * server);
+static void json_waiters_notify (UccsServer * server, gboolean unlocked);
+static GVariant * get_cached_domains (Server * server);
+static Server * find_uri (Server * server, const gchar * uri);
+static void set_last_used_server (Server * server, const gchar * uri);
+static void evaluate_state (UccsServer * server);
+static void nm_state_changed (NMClient *client, const GParamSpec *pspec, gpointer user_data);
+
+typedef struct _json_callback_t json_callback_t;
+struct _json_callback_t {
+ gchar * sender;
+ void (*callback) (UccsServer * server, gboolean unlocked, gpointer user_data);
+ gpointer userdata;
+};
+
+G_DEFINE_TYPE (UccsServer, uccs_server, SERVER_TYPE);
+
+/* Static global client so we don't keep reallocating them. We only need
+ one really */
+static NMClient * global_client = NULL;
+
+static void
+uccs_server_class_init (UccsServerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = uccs_server_dispose;
+ object_class->finalize = uccs_server_finalize;
+
+ ServerClass * server_class = SERVER_CLASS(klass);
+
+ server_class->get_properties = get_properties;
+ /* UCCS can't have applications */
+ server_class->get_applications = NULL;
+ server_class->get_domains = get_cached_domains;
+ server_class->find_uri = find_uri;
+ server_class->set_last_used_server = set_last_used_server;
+
+ return;
+}
+
+static void
+uccs_server_init (UccsServer *self)
+{
+ self->exec = g_find_program_in_path(UCCS_QUERY_TOOL);
+
+ self->username = NULL;
+ self->password = NULL;
+
+ self->lovers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ self->subservers = NULL;
+
+ self->json_waiters = NULL;
+ self->json_watch = 0;
+ self->json_pid = 0;
+
+ self->json_stream = NULL;
+ self->pass_stream = NULL;
+
+ self->min_network = NM_STATE_CONNECTED_GLOBAL;
+ self->last_network = NM_STATE_DISCONNECTED;
+ self->nm_client = NULL;
+ self->nm_signal = 0;
+
+ /* Start as unavailable */
+ self->parent.state = SERVER_STATE_UNAVAILABLE;
+
+ if (global_client == NULL) {
+ global_client = nm_client_new();
+
+ if (global_client != NULL) {
+ g_object_add_weak_pointer(G_OBJECT(global_client), (gpointer *)&global_client);
+ self->nm_client = global_client;
+ }
+ } else {
+ self->nm_client = g_object_ref(global_client);
+ }
+
+ if (self->nm_client != NULL) {
+ self->nm_signal = g_signal_connect(self->nm_client, "notify::" NM_CLIENT_STATE, G_CALLBACK(nm_state_changed), self);
+ }
+
+ self->verify_server = TRUE;
+ self->verified_server = FALSE;
+ self->session = NULL;
+
+ /* Need the soup session before the state changed */
+ self->session = soup_session_sync_new();
+
+ nm_state_changed(self->nm_client, NULL, self);
+ evaluate_state(self);
+
+ return;
+}
+
+/* Small function to try and figure out the state of the server and set the
+ status appropriately */
+static void
+evaluate_state (UccsServer * server)
+{
+ ServerState tempstate = SERVER_STATE_ALLGOOD;
+
+ if (server->exec == NULL) {
+ tempstate = SERVER_STATE_UNAVAILABLE;
+ }
+
+ if (server->last_network < server->min_network) {
+ tempstate = SERVER_STATE_UNAVAILABLE;
+ }
+
+ if (server->verify_server && !server->verified_server && server->min_network > NM_STATE_DISCONNECTED) {
+ tempstate = SERVER_STATE_UNAVAILABLE;
+ }
+
+ if (tempstate != server->parent.state) {
+ server->parent.state = tempstate;
+ g_signal_emit_by_name(server, SERVER_SIGNAL_STATE_CHANGED, server->parent.state);
+ }
+
+ return;
+}
+
+struct _hash_helper {
+ GVariant * params;
+ GDBusConnection * session;
+};
+
+/* GHashTable foreach item */
+static gboolean
+clear_hash_helper (gpointer key, gpointer value, gpointer user_data)
+{
+ struct _hash_helper * helper = (struct _hash_helper *)user_data;
+ GError * error = NULL;
+
+ g_dbus_connection_emit_signal(helper->session,
+ (const gchar *)key, /* dest */
+ "/com/canonical/RemoteLogin", /* object path */
+ "com.canonical.RemoteLogin", /* interface name */
+ "com.canonical.RemoteLogin.LoginChanged", /* signal name */
+ helper->params, /* params */
+ &error);
+
+ if (error != NULL) {
+ g_warning("Unable to signal UCCS server shutdown: %s", error->message);
+ g_error_free(error);
+ }
+
+ return TRUE;
+}
+
+/* Clear the hash table by going through it and signaling */
+static void
+clear_hash (UccsServer * server)
+{
+ if (g_hash_table_size(server->lovers) == 0) {
+ return;
+ }
+
+ g_return_if_fail(server->parent.uri != NULL);
+ g_return_if_fail(server->username != NULL);
+
+ GDBusConnection * session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); /* Shouldn't block, we should have it */
+ GVariant * param = g_variant_new("(ss)", server->parent.uri, server->username); /* params */
+ g_variant_ref_sink(param);
+
+ struct _hash_helper helper;
+ helper.params = param;
+ helper.session = session;
+
+ g_hash_table_foreach_remove(server->lovers, clear_hash_helper, &helper);
+
+ g_object_unref(session);
+ g_variant_unref(param);
+
+ return;
+}
+
+/* Clear the JSON task and waiters */
+static void
+clear_json (UccsServer * self)
+{
+ if (self->json_watch != 0) {
+ g_source_remove(self->json_watch);
+ self->json_watch = 0;
+ }
+
+ if (self->json_pid != 0) {
+ g_spawn_close_pid(self->json_pid);
+ self->json_pid = 0;
+ }
+
+ if (self->json_stream != NULL) {
+ g_input_stream_close(self->json_stream, NULL, NULL);
+ g_object_unref(self->json_stream);
+ self->json_stream = NULL;
+ }
+
+ if (self->pass_stream != NULL) {
+ g_output_stream_close(self->pass_stream, NULL, NULL);
+ g_object_unref(self->pass_stream);
+ self->pass_stream = NULL;
+ }
+
+ json_waiters_notify(self, FALSE);
+
+ return;
+}
+
+/* Clean up references */
+static void
+uccs_server_dispose (GObject *object)
+{
+ UccsServer * self = UCCS_SERVER(object);
+
+ g_clear_object(&self->session);
+
+ if (self->nm_signal != 0) {
+ g_signal_handler_disconnect(self->nm_client, self->nm_signal);
+ self->nm_signal = 0;
+ }
+
+ g_clear_object(&self->nm_client);
+
+ clear_json(self);
+
+ if (self->lovers != NULL) {
+ clear_hash(self);
+ }
+
+ g_list_free_full(self->subservers, g_object_unref);
+ self->subservers = NULL; /* Ironically the free function is the only GList
+ function that doesn't return a new pointer by itself */
+
+ G_OBJECT_CLASS (uccs_server_parent_class)->dispose (object);
+ return;
+}
+
+/* Clean up memory */
+static void
+uccs_server_finalize (GObject *object)
+{
+ UccsServer * self = UCCS_SERVER(object);
+
+ g_free(self->exec); self->exec = NULL;
+ g_free(self->username); self->username = NULL;
+ g_free(self->password); self->password = NULL;
+
+ if (self->lovers != NULL) {
+ g_hash_table_unref(self->lovers);
+ self->lovers = NULL;
+ }
+
+ G_OBJECT_CLASS (uccs_server_parent_class)->finalize (object);
+ return;
+}
+
+/* Callback from the message getting complete */
+static void
+verify_server_cb (SoupSession * session, SoupMessage * message, gpointer user_data)
+{
+ UccsServer * server = UCCS_SERVER(user_data);
+ guint statuscode = 404;
+
+ g_object_get(G_OBJECT(message), SOUP_MESSAGE_STATUS_CODE, &statuscode, NULL);
+ g_debug("Verification came back with status: %d", statuscode);
+
+ if (statuscode == 200) {
+ server->verified_server = TRUE;
+ } else {
+ server->verified_server = FALSE;
+ }
+
+ evaluate_state(server);
+
+ return;
+}
+
+/* Set up the process to verify the server */
+static void
+verify_server (UccsServer * server)
+{
+ g_return_if_fail(server->session != NULL);
+
+ if (server->parent.uri == NULL) {
+ return;
+ }
+
+ SoupMessage * message = soup_message_new("HEAD", server->parent.uri);
+ soup_session_queue_message(server->session, message, verify_server_cb, server);
+ g_debug("Getting HEAD from: %s", server->parent.uri);
+
+ return;
+}
+
+/* Callback for when the Network Manger state changes */
+static void
+nm_state_changed (NMClient *client, const GParamSpec *pspec, gpointer user_data)
+{
+ g_return_if_fail(IS_UCCS_SERVER(user_data));
+ UccsServer * server = UCCS_SERVER(user_data);
+
+ if (server->nm_client == NULL || !nm_client_get_manager_running(server->nm_client)) {
+ server->last_network = NM_STATE_DISCONNECTED;
+ } else {
+ server->last_network = nm_client_get_state(server->nm_client);
+ }
+
+ if (server->last_network == NM_STATE_DISCONNECTED) {
+ server->verified_server = FALSE;
+ soup_session_abort(server->session);
+ }
+
+ if (server->last_network == NM_STATE_CONNECTED_GLOBAL && server->verify_server && !server->verified_server) {
+ verify_server(server);
+ }
+
+ evaluate_state(server);
+
+ return;
+}
+
+/* Get the properties that can be sent by the greeter for this server */
+static GVariant *
+get_properties (Server * server)
+{
+ GVariantBuilder propbuilder;
+ g_variant_builder_init(&propbuilder, G_VARIANT_TYPE_ARRAY);
+
+ GVariantBuilder namebuilder;
+ g_variant_builder_init(&namebuilder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value(&namebuilder, g_variant_new_string("email"));
+ g_variant_builder_add_value(&namebuilder, g_variant_new_boolean(TRUE));
+ g_variant_builder_add_value(&namebuilder, g_variant_new_variant(g_variant_new_string("")));
+ g_variant_builder_add_value(&namebuilder, g_variant_parse(G_VARIANT_TYPE_VARDICT, "{}", NULL, NULL, NULL));
+ g_variant_builder_add_value(&propbuilder, g_variant_builder_end(&namebuilder));
+
+ GVariantBuilder passbuilder;
+ g_variant_builder_init(&passbuilder, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value(&passbuilder, g_variant_new_string("password"));
+ g_variant_builder_add_value(&passbuilder, g_variant_new_boolean(TRUE));
+ g_variant_builder_add_value(&passbuilder, g_variant_new_variant(g_variant_new_string("")));
+ g_variant_builder_add_value(&passbuilder, g_variant_parse(G_VARIANT_TYPE_VARDICT, "{}", NULL, NULL, NULL));
+ g_variant_builder_add_value(&propbuilder, g_variant_builder_end(&passbuilder));
+
+ return g_variant_builder_end(&propbuilder);
+}
+
+/* Set the exec value for the server */
+const gchar *
+uccs_server_set_exec (UccsServer * server, const gchar * exec)
+{
+ g_return_val_if_fail(IS_UCCS_SERVER(server), NULL);
+
+ g_clear_pointer(&server->exec, g_free);
+
+ if (exec != NULL) {
+ server->exec = g_find_program_in_path(exec);
+ }
+
+ evaluate_state(server);
+
+ return server->exec;
+}
+
+/* Build a new uccs server from a keyfile and a group in it */
+Server *
+uccs_server_new_from_keyfile (GKeyFile * keyfile, const gchar * groupname)
+{
+ g_return_val_if_fail(keyfile != NULL, NULL); /* NOTE: No way to check if that's really a keyfile :-( */
+ g_return_val_if_fail(groupname != NULL, NULL);
+
+ if (!g_key_file_has_group(keyfile, groupname)) {
+ g_warning("Server specified but group '%s' was not found", groupname);
+ return NULL;
+ }
+
+ UccsServer * server = g_object_new(UCCS_SERVER_TYPE, NULL);
+
+ if (g_key_file_has_key(keyfile, groupname, CONFIG_SERVER_NAME, NULL)) {
+ gchar * keyname = g_key_file_get_string(keyfile, groupname, CONFIG_SERVER_NAME, NULL);
+ server->parent.name = g_strdup(_(keyname));
+ g_free(keyname);
+ }
+
+ if (g_key_file_has_key(keyfile, groupname, CONFIG_SERVER_URI, NULL)) {
+ server->parent.uri = g_key_file_get_string(keyfile, groupname, CONFIG_SERVER_URI, NULL);
+ }
+
+ if (g_key_file_has_key(keyfile, groupname, CONFIG_UCCS_EXEC, NULL)) {
+ gchar * key = g_key_file_get_string(keyfile, groupname, CONFIG_UCCS_EXEC, NULL);
+ uccs_server_set_exec(server, key);
+ g_free(key);
+ }
+
+ if (g_key_file_has_key(keyfile, groupname, CONFIG_UCCS_NETWORK, NULL)) {
+ gchar * key = g_key_file_get_string(keyfile, groupname, CONFIG_UCCS_NETWORK, NULL);
+
+ if (g_strcmp0(key, CONFIG_UCCS_NETWORK_NONE) == 0) {
+ server->min_network = NM_STATE_DISCONNECTED;
+ } else if (g_strcmp0(key, CONFIG_UCCS_NETWORK_GLOBAL) == 0) {
+ server->min_network = NM_STATE_CONNECTED_GLOBAL;
+ }
+ /* NOTE: There is a possibility for other network types to be added here,
+ but they can be tricky to test. Feel free to patch it, but please include
+ those tests :-) */
+
+ g_free(key);
+ }
+
+ if (g_key_file_has_key(keyfile, groupname, CONFIG_UCCS_VERIFY, NULL)) {
+ server->verify_server = g_key_file_get_boolean(keyfile, groupname, CONFIG_UCCS_VERIFY, NULL);
+ }
+
+ evaluate_state(server);
+
+ return SERVER(server);
+}
+
+/* Look at the array of RLS data and build a server for each entry
+ in the array */
+static gboolean
+parse_rds_array (UccsServer * server, JsonArray * array)
+{
+ int i;
+ for (i = 0; i < json_array_get_length(array); i++) {
+ JsonNode * node = json_array_get_element(array, i);
+
+ if (JSON_NODE_TYPE(node) != JSON_NODE_OBJECT) {
+ continue;
+ }
+
+ JsonObject * object = json_node_get_object(node);
+ Server * newserver = server_new_from_json(object);
+ if (newserver != NULL) {
+ server->subservers = g_list_append(server->subservers, newserver);
+ }
+ }
+
+ return TRUE;
+}
+
+/* Parse the JSON content and allocate servers based on that */
+static gboolean
+parse_json (UccsServer * server, GInputStream * json)
+{
+ if (json == NULL) return FALSE; /* Shouldn't happen, but let's just handle it */
+
+ gboolean passed = TRUE;
+ JsonParser * parser = json_parser_new();
+ GError * error = NULL;
+
+ if (!json_parser_load_from_stream(parser, json, NULL, &error)) {
+ g_warning("Unable to parse JSON data: %s", error->message);
+ g_error_free(error);
+ error = NULL;
+ passed = FALSE;
+ }
+
+ /* Make sure we have a sane root node */
+ JsonNode * root_node = NULL;
+ if (passed) {
+ root_node = json_parser_get_root(parser);
+
+#if 0
+ JsonGenerator * gen = json_generator_new();
+ json_generator_set_root(gen, root_node);
+ gchar * data = json_generator_to_data(gen, NULL);
+ g_debug("%s", data);
+ g_free(data);
+ g_object_unref(G_OBJECT(gen));
+#endif
+ }
+ if (root_node != NULL && JSON_NODE_TYPE(root_node) != JSON_NODE_OBJECT) {
+ g_warning("Root node of JSON data is not an object. It is: %s", json_node_type_name(root_node));
+ passed = FALSE;
+ }
+
+ /* Take our object and see if it has the property that we need */
+ JsonObject * root_object = NULL;
+ if (passed) {
+ root_object = json_node_get_object(root_node);
+ }
+ if (root_object != NULL && json_object_has_member(root_object, "RemoteDesktopServers")) {
+ /* This shows that we have some. It's okay if there aren't any. Seems
+ a bit silly, but we're not bitching too much. */
+ JsonNode * rds_node = json_object_get_member(root_object, "RemoteDesktopServers");
+ if (JSON_NODE_TYPE(rds_node) == JSON_NODE_ARRAY) {
+ JsonArray * rds_array = json_node_get_array(rds_node);
+ passed = parse_rds_array(server, rds_array);
+ } else {
+ /* Okay we're a little bit angrier about this one */
+ g_warning("Malformed 'RemoteDesktopServer' entry. Not an array but a: %s", json_node_type_name(rds_node));
+ passed = FALSE;
+ }
+
+ if (json_object_has_member(root_object, "DefaultServer")) {
+ JsonNode * ds_node = json_object_get_member(root_object, "DefaultServer");
+ if (JSON_NODE_TYPE(ds_node) == JSON_NODE_VALUE && json_node_get_value_type(ds_node) == G_TYPE_STRING) {
+ const gchar * default_server_name = json_node_get_string(ds_node);
+ if (default_server_name != NULL) {
+ GList * lserver;
+ for (lserver = server->subservers; lserver != NULL; lserver = g_list_next(lserver)) {
+ Server * serv = SERVER(lserver->data);
+
+ if (g_strcmp0(serv->name, default_server_name) == 0) {
+ serv->last_used = TRUE;
+ break;
+ }
+ }
+ if (lserver == NULL && strlen(default_server_name) > 0) {
+ g_warning("Could not find the 'DefaultServer' server.");
+ passed = FALSE;
+ }
+ }
+ } else {
+ g_warning("Malformed 'DefaultServer' entry. Not a string value");
+ passed = FALSE;
+ }
+ }
+ } else {
+ g_debug("No 'RemoteDesktopServers' found");
+ }
+
+ g_object_unref(parser);
+ return passed;
+}
+
+/* Go through the waiters and notify them of the status */
+static void
+json_waiters_notify (UccsServer * server, gboolean unlocked)
+{
+ /* NOTE: Taking the list as the call back might add themselves to
+ the list so we don't want to have it corrupted in the middle of
+ the execution of this function */
+ GList * waiters = server->json_waiters;
+ server->json_waiters = NULL;
+
+ while (waiters != NULL) {
+ json_callback_t * json_callback = (json_callback_t *)waiters->data;
+
+ if (unlocked) {
+ g_hash_table_insert(server->lovers, g_strdup(json_callback->sender), GINT_TO_POINTER(TRUE));
+ }
+
+ if (json_callback->callback != NULL) {
+ json_callback->callback(server, unlocked, json_callback->userdata);
+ }
+
+ g_free(json_callback->sender);
+ g_free(json_callback);
+ waiters = g_list_delete_link(waiters, waiters);
+ }
+
+ return;
+}
+
+/* Callback from when we know that we've got all the JSON we're
+ gonna get */
+static void
+json_grab_cb (GPid pid, gint status, gpointer user_data)
+{
+ UccsServer * server = UCCS_SERVER(user_data);
+
+ server->json_pid = 0;
+ server->json_watch = 0;
+
+ if (status == 0) {
+ gboolean parser = parse_json(server, server->json_stream);
+
+ json_waiters_notify(server, parser);
+ } else {
+ g_free(server->username);
+ server->username = NULL;
+ g_free(server->password);
+ server->password = NULL;
+
+ json_waiters_notify(server, FALSE);
+ }
+
+ /* Drop the Streams -- NOTE: DO NOT CROSS THE STREAMS */
+ g_output_stream_close(server->pass_stream, NULL, NULL);
+ g_object_unref(server->pass_stream);
+ server->pass_stream = NULL;
+
+ g_input_stream_close(server->json_stream, NULL, NULL);
+ g_object_unref(server->json_stream);
+ server->json_stream = NULL;
+
+ g_spawn_close_pid(pid);
+
+ return;
+}
+
+/* This is the callback for writing the password, it mostly exists
+ to free the buffer, but we'll print some info as well just because
+ we can */
+static void
+password_write_cb (GObject * src_obj, GAsyncResult * res, gpointer user_data)
+{
+ /* Kill the buffer */
+ g_free(user_data);
+
+ GError * error = NULL;
+ g_output_stream_write_finish(G_OUTPUT_STREAM(src_obj), res, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to write password to UCCS process: %s", error->message);
+ g_error_free(error);
+ } else {
+ g_debug("Wrote password to UCCS process");
+ g_output_stream_close(G_OUTPUT_STREAM(src_obj), NULL, NULL);
+ }
+
+ return;
+}
+
+/**
+ * uccs_server_unlock:
+ * @server: The server to unlock
+ * @address: DBus address of the person unlocking us
+ * @username: Username for the UCCS
+ * @password: (allow-none) Password to use
+ * @allowcache: If using cache is allowed
+ * @callback: Function to call when we have an answer
+ * @user_data: Data for the callback
+ *
+ * Unlocks the UCCS server making servers available either from the
+ * cache or from the network.
+ */
+void
+uccs_server_unlock (UccsServer * server, const gchar * address, const gchar * username, const gchar * password, gboolean allowcache, void (*callback) (UccsServer * server, gboolean unlocked, gpointer user_data), gpointer user_data)
+{
+ g_return_if_fail(IS_UCCS_SERVER(server));
+ g_return_if_fail(username != NULL);
+ g_return_if_fail(address != NULL);
+
+ /* Check the current values we have, they might be NULL, which in
+ that case they won't match */
+ if (allowcache && g_strcmp0(username, server->username) == 0 &&
+ g_strcmp0(password, server->password) == 0) {
+ g_hash_table_insert(server->lovers, g_strdup(address), GINT_TO_POINTER(TRUE));
+
+ if (callback != NULL) {
+ callback(server, TRUE, user_data);
+ }
+
+ return;
+ }
+
+ g_return_if_fail(server->exec != NULL); /* Shouldn't happen, but I'd feel safer if we checked */
+
+ /* If we're not going to allow the cache, just clear it right away */
+ if (!allowcache) {
+ clear_hash(server);
+ }
+
+ /* We're changing the username and password, if there were other
+ people who had it, they need to know we're different now */
+ if (g_strcmp0(username, server->username) != 0 ||
+ g_strcmp0(password, server->password) != 0) {
+ clear_hash(server);
+ clear_json(server);
+
+ g_clear_pointer(&server->username, g_free);
+ g_clear_pointer(&server->password, g_free);
+
+ server->username = g_strdup(username);
+ server->password = g_strdup(password);
+ }
+
+ /* Add ourselves to the queue */
+ json_callback_t * json_callback = g_new0(json_callback_t, 1);
+ json_callback->sender = g_strdup(address);
+ json_callback->callback = callback;
+ json_callback->userdata = user_data;
+
+ server->json_waiters = g_list_append(server->json_waiters, json_callback);
+
+ if (server->json_pid == 0) {
+ gint std_in, std_out;
+ GError * error = NULL;
+
+ const gchar * argv[3];
+ argv[0] = server->exec;
+ argv[1] = server->username;
+ argv[2] = NULL;
+
+ g_spawn_async_with_pipes(NULL, /* pwd */
+ (gchar **)argv,
+ NULL, /* env */
+ G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, /* child setup */
+ &server->json_pid,
+ &std_in,
+ &std_out,
+ NULL, /* stderr */
+ &error); /* error */
+
+ if (error != NULL) {
+ g_warning("Unable to start UCCS process: %s", error->message);
+ g_error_free(error);
+ server->json_pid = 0; /* really shouldn't get changed, but since we're using it to detect if it's running, let's double check, eh? */
+ json_waiters_notify(server, FALSE);
+ } else {
+ /* Watch for when it's done */
+ server->json_watch = g_child_watch_add(server->json_pid, json_grab_cb, server);
+
+ /* Set up I/O streams */
+ server->json_stream = g_unix_input_stream_new(std_out, TRUE);
+ server->pass_stream = g_unix_output_stream_new(std_in, TRUE);
+
+ gchar * pass = g_strdup(server->password);
+ g_output_stream_write_async(server->pass_stream,
+ pass,
+ strlen(pass), /* number of bytes */
+ G_PRIORITY_DEFAULT, /* priority */
+ NULL, /* cancellable */
+ password_write_cb,
+ pass);
+ }
+ }
+
+ return;
+}
+
+/* A little quickie function to handle the null server array */
+inline static GVariant *
+null_server_array (void)
+{
+ return g_variant_new_array(G_VARIANT_TYPE("(sssba(sbva{sv})a(si))"),
+ NULL, 0);
+}
+
+/**
+ * uccs_server_get_servers:
+ * @server: Server to get our list from
+ * @address: Who's asking
+ *
+ * Will get a valid variant with servers. If the asker hasn't unlocked us
+ * then the list will always be empty.
+ *
+ * Return value: A variant array
+ */
+GVariant *
+uccs_server_get_servers (UccsServer * server, const gchar * address)
+{
+ g_return_val_if_fail(IS_UCCS_SERVER(server), null_server_array());
+ g_return_val_if_fail(address != NULL, null_server_array());
+
+ if (!GPOINTER_TO_INT(g_hash_table_lookup(server->lovers, address))) {
+ g_warning("Address '%s' is not authorized", address);
+ return null_server_array();
+ }
+
+ gchar *last_used_server_name = NULL;
+ if (server->username != NULL && server->password != NULL) {
+ gchar *username_sha = g_compute_checksum_for_string (G_CHECKSUM_SHA256, server->username, -1);
+ gchar *file_path = g_build_path ("/", g_get_user_cache_dir(), "remote-login-service", "cache", username_sha, NULL);
+ gchar *encryptedContents;
+ gsize encryptedContentsLength;
+ if (g_file_get_contents (file_path, &encryptedContents, &encryptedContentsLength, NULL)) {
+ gchar *file_contents = do_aes_decrypt(encryptedContents, server->password, encryptedContentsLength);
+ g_free (encryptedContents);
+ if (file_contents != NULL) {
+ GKeyFile * key_file = g_key_file_new();
+ if (g_key_file_load_from_data (key_file, file_contents, strlen (file_contents), G_KEY_FILE_NONE, NULL)) {
+ last_used_server_name = g_key_file_get_string (key_file, server->parent.name, "last_used", NULL);
+ }
+ g_key_file_free (key_file);
+ g_free (file_contents);
+ }
+ }
+ g_free (username_sha);
+ g_free (file_path);
+ }
+
+ GVariantBuilder array;
+ g_variant_builder_init(&array, G_VARIANT_TYPE_ARRAY);
+ GList * lserver;
+ gint servercnt = 0;
+
+ Server * last_used_server = NULL;
+ if (last_used_server_name != NULL) {
+ for (lserver = server->subservers; last_used_server == NULL && lserver != NULL; lserver = g_list_next(lserver)) {
+ Server * serv = SERVER(lserver->data);
+
+ /* We only want servers that are all good */
+ if (serv->state != SERVER_STATE_ALLGOOD) {
+ continue;
+ }
+ if (g_strcmp0(serv->name, last_used_server_name) == 0)
+ last_used_server = serv;
+ }
+ }
+ g_free (last_used_server_name);
+
+ for (lserver = server->subservers; lserver != NULL; lserver = g_list_next(lserver)) {
+ Server * serv = SERVER(lserver->data);
+
+ /* We only want servers that are all good */
+ if (serv->state != SERVER_STATE_ALLGOOD) {
+ continue;
+ }
+
+ if (last_used_server != NULL)
+ serv->last_used = last_used_server == serv;
+
+ servercnt++;
+ g_variant_builder_add_value(&array, server_get_variant(serv));
+ }
+
+ if (servercnt == 0) {
+ g_variant_builder_clear(&array);
+ return null_server_array();
+ }
+
+ return g_variant_builder_end(&array);
+}
+
+/* Returns the cached domains as an array. Not currently with any of
+ this cached currently, so it's a NULL array. */
+static GVariant *
+get_cached_domains (Server * server)
+{
+ g_return_val_if_fail(IS_UCCS_SERVER(server), NULL);
+ return g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0);
+}
+
+/* Tail recursive function to look at a list entry and see
+ if that server matches a URI, or go down the list */
+static Server *
+find_uri_helper (GList * list, const gchar * uri)
+{
+ if (list == NULL) return NULL;
+
+ Server * inserver = SERVER(list->data);
+
+ if (inserver == NULL) {
+ return find_uri_helper(g_list_next(list), uri);
+ }
+
+ Server * outserver = server_find_uri(inserver, uri);
+
+ if (outserver != NULL) {
+ return outserver;
+ }
+
+ return find_uri_helper(g_list_next(list), uri);
+}
+
+/* Look through our subservers to see if any of them match this
+ URI either */
+static Server *
+find_uri (Server * server, const gchar * uri)
+{
+ g_return_val_if_fail(IS_UCCS_SERVER(server), NULL);
+ /* If it is this server that's handled by the super class */
+ return find_uri_helper(UCCS_SERVER(server)->subservers, uri);
+}
+
+/* Look through our subservers to see if any of them match this
+ URI and set as used */
+static void
+set_last_used_server (Server * server, const gchar * uri)
+{
+ Server * subserver = server_find_uri(server, uri);
+
+ if (subserver != NULL) {
+ subserver->last_used = TRUE;
+
+ /* Write to disk */
+ if (UCCS_SERVER(server)->username != NULL && UCCS_SERVER(server)->password) {
+ GKeyFile * key_file = g_key_file_new();
+ g_key_file_set_string (key_file, server->name, "last_used", subserver->name);
+ gsize data_length;
+ gchar *data = g_key_file_to_data (key_file, &data_length, NULL);
+ g_key_file_free (key_file);
+
+ size_t enc_data_length;
+ gchar *enc_data = do_aes_encrypt(data, UCCS_SERVER(server)->password, &enc_data_length);
+ g_free (data);
+
+ gchar *dir_path = g_build_path ("/", g_get_user_cache_dir(), "remote-login-service", "cache", NULL);
+ gint status = g_mkdir_with_parents (dir_path, 0700);
+ if (status == 0)
+ {
+ gchar *username_sha = g_compute_checksum_for_string (G_CHECKSUM_SHA256, UCCS_SERVER(server)->username, -1);
+ gchar *path = g_build_path ("/", dir_path, username_sha, NULL);
+ gboolean success = g_file_set_contents (path, enc_data, enc_data_length, NULL);
+ if (!success) {
+ g_warning("Failing writting cache data to '%s'.", path);
+ }
+ g_free (username_sha);
+ g_free (path);
+ }
+ else
+ {
+ g_warning("Failed to create '%s'.", dir_path);
+ }
+ g_free (enc_data);
+ g_free (dir_path);
+ }
+ }
+}