diff options
Diffstat (limited to 'src/main.c')
-rw-r--r-- | src/main.c | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..b6d56d4 --- /dev/null +++ b/src/main.c @@ -0,0 +1,453 @@ +/* + * 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.h> +#include <glib/gi18n.h> + +/* NOTE: Required to build without optimizations */ +#include <locale.h> + +#include "remote-login.h" +#include "defines.h" + +#include "server.h" +#include "rdp-server.h" +#include "citrix-server.h" +#include "uccs-server.h" +#include "crypt.h" + +gint server_list_to_array (GVariantBuilder * builder, GList * items); + +enum { + ERROR_SERVER_URI, + ERROR_LOGIN +}; + +GList * config_file_servers = NULL; + +/* Get the error domain for this module */ +static GQuark +error_domain (void) +{ + static GQuark value = 0; + if (value == 0) { + value = g_quark_from_static_string("remote-login-service"); + } + return value; +} + +/* When one of the state changes on the server emit that so that everone knows there + might be a new server available. */ +static void +server_status_updated (Server * server, ServerState newstate, RemoteLogin * rl) +{ + GVariant * array = NULL; + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + + if (server_list_to_array(&builder, config_file_servers) > 0) { + array = g_variant_builder_end(&builder); + } else { + g_variant_builder_clear(&builder); + array = g_variant_new_array(G_VARIANT_TYPE("(sssba(sbva{sv})a(si))"), NULL, 0); + } + + remote_login_emit_servers_updated(rl, array); + return; +} + +/* Looks for the config file and does some basic parsing to pull out the UCCS servers + that are configured in it */ +static void +find_config_file (GKeyFile * parsed, const gchar * cmnd_line, RemoteLogin * rl) +{ + GError * error = NULL; + const gchar * file = DEFAULT_CONFIG_FILE; + + if (cmnd_line != NULL) { + file = cmnd_line; + } + + if (!g_key_file_load_from_file(parsed, file, G_KEY_FILE_NONE, &error)) { + g_warning("Unable to parse config file '%s': %s", file, error->message); + g_error_free(error); + return; + } + + if (!g_key_file_has_group(parsed, CONFIG_MAIN_GROUP)) { + g_warning("Config file '%s' doesn't have group '" CONFIG_MAIN_GROUP "'", file); + /* Probably should clear the keyfile, but there doesn't seem to be a way to do that */ + return; + } + + if (g_key_file_has_key(parsed, CONFIG_MAIN_GROUP, CONFIG_MAIN_SERVERS, NULL)) { + gchar ** grouplist = g_key_file_get_string_list(parsed, CONFIG_MAIN_GROUP, CONFIG_MAIN_SERVERS, NULL, NULL); + int i = 0; + gchar * groupsuffix; + + for (groupsuffix = grouplist[0], i = 0; groupsuffix != NULL; groupsuffix = grouplist[++i]) { + gchar * groupname = g_strdup_printf("%s %s", CONFIG_SERVER_PREFIX, groupsuffix); + Server * server = server_new_from_keyfile(parsed, groupname); + g_free(groupname); + + if (server == NULL) { + /* Assume a relevant error is printed above */ + continue; + } + + config_file_servers = g_list_append(config_file_servers, server); + g_signal_connect(server, SERVER_SIGNAL_STATE_CHANGED, G_CALLBACK(server_status_updated), rl); + } + + g_strfreev(grouplist); + } + + /* Signal the list of servers so that we're sure everyone's got them. This is to + solve a possible race where someone could ask while we're configuring these. */ + server_status_updated(NULL, SERVER_STATE_ALLGOOD, rl); + return; +} + +gint +server_list_to_array (GVariantBuilder * builder, GList * items) +{ + gint servercnt = 0; + GList * head = NULL; + for (head = items; head != NULL; head = g_list_next(head)) { + Server * server = SERVER(head->data); + + /* We only want servers that are all good */ + if (server->state != SERVER_STATE_ALLGOOD) { + continue; + } + + servercnt++; + GVariant * variant = server_get_variant(server); + g_variant_builder_add_value(builder, variant); + } + + return servercnt; +} + +static gboolean +handle_get_servers (RemoteLogin * rl, GDBusMethodInvocation * invocation, gpointer user_data) +{ + GVariant * array = NULL; + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + + if (server_list_to_array(&builder, config_file_servers) > 0) { + array = g_variant_builder_end(&builder); + } else { + g_variant_builder_clear(&builder); + array = g_variant_new_array(G_VARIANT_TYPE("(sssba(sbva{sv})a(si))"), NULL, 0); + } + + g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&array, 1)); + + return TRUE; +} + +/* Handle the situation of whether we unlock or not and respond over + DBus with either an error or the list of servers. */ +static void +handle_get_servers_login_cb (UccsServer * server, gboolean unlocked, gpointer user_data) +{ + GDBusMethodInvocation * invocation = (GDBusMethodInvocation *)user_data; + const gchar * sender = g_dbus_method_invocation_get_sender(invocation); + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE); + + /* Signal whether we're unlocked */ + g_variant_builder_add_value(&builder, g_variant_new_boolean(unlocked)); + + /* Only network, no caching yet */ + g_variant_builder_add_value(&builder, g_variant_new_string("network")); + + /* Get the array of servers */ + GVariant * array = uccs_server_get_servers(server, sender); + g_variant_builder_add_value(&builder, array); + + g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder)); + return; +} + +/* Handle the GetServerForLogin DBus call */ +static gboolean +handle_get_servers_login (RemoteLogin * rl, GDBusMethodInvocation * invocation, gpointer user_data) +{ + GVariant * params = g_dbus_method_invocation_get_parameters(invocation); + const gchar * sender = g_dbus_method_invocation_get_sender(invocation); + + GVariant * child = NULL; + const gchar * uri = NULL; + + child = g_variant_get_child_value(params, 0); + uri = g_variant_get_string(child, NULL); + g_variant_unref(child); /* fine as we know params is still ref'd */ + + GList * lserver = NULL; + Server * server = NULL; + for (lserver = config_file_servers; lserver != NULL; lserver = g_list_next(lserver)) { + server = SERVER(lserver->data); + + if (server == NULL) { + continue; + } + + if (!IS_UCCS_SERVER(server)) { + continue; + } + + if (g_strcmp0(server->uri, uri) == 0) { + break; + } + } + + if (lserver == NULL) { + /* Couldn't find something with that URI, we're done, thanks. */ + g_dbus_method_invocation_return_error(invocation, + error_domain(), + ERROR_SERVER_URI, + "Unable to find a server with the URI: '%s'", + uri); + + return TRUE; + } + + /* Unlock the Server */ + const gchar * username = NULL; + const gchar * password = NULL; + gboolean allowcache = FALSE; + + child = g_variant_get_child_value(params, 1); + username = g_variant_get_string(child, NULL); + g_variant_unref(child); /* fine as we know params is still ref'd */ + + child = g_variant_get_child_value(params, 2); + password = g_variant_get_string(child, NULL); + g_variant_unref(child); /* fine as we know params is still ref'd */ + + child = g_variant_get_child_value(params, 3); + allowcache = g_variant_get_boolean(child); + g_variant_unref(child); + + /* Try to login and mark us as servicing the message */ + uccs_server_unlock(UCCS_SERVER(server), sender, username, password, allowcache, handle_get_servers_login_cb, invocation); + return TRUE; +} + +/* Look through a list of servers to see if one matches a URL */ +static Server * +handle_get_domains_list_helper (GList * list, const gchar * uri) +{ + if (list == NULL) return NULL; + + Server * inserver = SERVER(list->data); + + if (inserver == NULL) { + return handle_get_domains_list_helper(g_list_next(list), uri); + } + + Server * outserver = server_find_uri(inserver, uri); + + if (outserver != NULL) { + return outserver; + } + + return handle_get_domains_list_helper(g_list_next(list), uri); +} + +/* Get the cached domains for a server */ +static gboolean +handle_get_domains (RemoteLogin * rl, GDBusMethodInvocation * invocation, gpointer user_data) +{ + GVariant * params = g_dbus_method_invocation_get_parameters(invocation); + + GVariant * child = NULL; + const gchar * uri = NULL; + + child = g_variant_get_child_value(params, 0); + uri = g_variant_get_string(child, NULL); + g_variant_unref(child); /* fine as we know params is still ref'd */ + + Server * server = handle_get_domains_list_helper(config_file_servers, uri); + + GVariant * domains = NULL; + if (server != NULL) { + domains = server_cached_domains(server); + } else { + domains = g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0); + } + + if (domains == NULL) { + /* Couldn't find something with that URI, we're done, thanks. */ + g_dbus_method_invocation_return_error(invocation, + error_domain(), + ERROR_SERVER_URI, + "Unable to find a server with the URI: '%s'", + uri); + + return TRUE; + } + + g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&domains, 1)); + + return TRUE; +} + +/* Set a given server as last used */ +static gboolean +handle_set_last_used_server (RemoteLogin * rl, GDBusMethodInvocation * invocation, gpointer user_data) +{ + GVariant * params = g_dbus_method_invocation_get_parameters(invocation); + + GVariant * child = NULL; + const gchar * uccsUri = NULL; + const gchar * serverUri = NULL; + + child = g_variant_get_child_value(params, 0); + uccsUri = g_variant_get_string(child, NULL); + g_variant_unref(child); /* fine as we know params is still ref'd */ + + child = g_variant_get_child_value(params, 1); + serverUri = g_variant_get_string(child, NULL); + g_variant_unref(child); /* fine as we know params is still ref'd */ + + GList * lserver = NULL; + Server * server = NULL; + for (lserver = config_file_servers; lserver != NULL; lserver = g_list_next(lserver)) { + server = SERVER(lserver->data); + + if (server == NULL) { + continue; + } + + if (!IS_UCCS_SERVER(server)) { + continue; + } + + if (g_strcmp0(server->uri, uccsUri) == 0) { + break; + } + } + + if (server != NULL) { + server_set_last_used_server (server, serverUri); + } + + g_dbus_method_invocation_return_value(invocation, NULL); + + return TRUE; +} + +/* If we loose the name, tell the world and there's not much we can do */ +static void +name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data) +{ + GMainLoop * mainloop = (GMainLoop *)user_data; + + g_warning("Unable to get name '%s'. Exiting.", name); + g_main_loop_quit(mainloop); + + return; +} + +static gchar * cmnd_line_config = NULL; + +static GOptionEntry general_options[] = { + {"config-file", 'c', 0, G_OPTION_ARG_FILENAME, &cmnd_line_config, N_("Configuration file for the remote login service. Defaults to '/etc/remote-login-service.conf'."), N_("key_file")}, + {NULL} +}; + +int +main (int argc, char * argv[]) +{ + GError * error = NULL; + + /* Init the GTypes */ + g_type_init(); + + /* Setup i18n */ + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + textdomain (GETTEXT_PACKAGE); + + /* Create our global variables */ + GKeyFile * config = g_key_file_new(); + GMainLoop * mainloop = g_main_loop_new(NULL, FALSE); + + /* Handle command line parameters */ + GOptionContext * context; + context = g_option_context_new(_("- Determine the remote servers that can be logged into")); + g_option_context_add_main_entries(context, general_options, "remote-login-service"); + + if (!g_option_context_parse(context, &argc, &argv, &error)) { + g_print("option parsing failed: %s\n", error->message); + g_error_free(error); + return 1; + } + + /* Start up D' Bus */ + GDBusConnection * session_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL /* cancel */, &error); + if (error != NULL) { + g_error("Unable to get session bus: %s", error->message); + g_error_free(error); + return -1; + } + + /* Build Dbus Interface */ + RemoteLogin * skel = remote_login_skeleton_new(); + /* Export it */ + g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(skel), + session_bus, + "/com/canonical/RemoteLogin", + NULL); + g_signal_connect(skel, "handle-get-servers", G_CALLBACK(handle_get_servers), NULL); + g_signal_connect(skel, "handle-get-servers-for-login", G_CALLBACK(handle_get_servers_login), NULL); + g_signal_connect(skel, "handle-get-cached-domains-for-server", G_CALLBACK(handle_get_domains), NULL); + g_signal_connect(skel, "handle-set-last-used-server", G_CALLBACK(handle_set_last_used_server), NULL); + + g_bus_own_name_on_connection(session_bus, + "com.canonical.RemoteLogin", + G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, /* aquired handler */ + name_lost, + mainloop, + NULL); /* mainloop free */ + + /* Parse config file */ + find_config_file(config, cmnd_line_config, skel); + + /* Loop forever */ + g_main_loop_run(mainloop); + + g_main_loop_unref(mainloop); + g_object_unref(config); + + g_free(cmnd_line_config); + + return 0; +} |