From fdc39509763f7d60429b903474916684da6653eb Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Sun, 2 Nov 2014 20:44:45 +0100 Subject: Imported Upstream version 1.0.0 --- src/uccs-server.c | 949 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 949 insertions(+) create mode 100644 src/uccs-server.c (limited to 'src/uccs-server.c') 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 . + * + * Author: Ted Gould + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include +#include + +#include +#include + +#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); + } + } +} -- cgit v1.2.3