/* * Copyright © 2012 Canonical Ltd. * Copyright © 2015 The Arctica Project * * 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 . * * Authors: Ted Gould * Mike Gabriel */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include /* NOTE: Required to build without optimizations */ #include #include "remote-logon.h" #include "defines.h" #include "server.h" #include "rdp-server.h" #include "citrix-server.h" #include "uccs-server.h" #include "x2go-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-logon-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 RLS_UNUSED *server, ServerState newstate, RemoteLogon *rl) { GVariant * array = NULL; GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); gint servers = server_list_to_array(&builder, config_file_servers); if (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_debug ("%d server(s) available", servers); g_debug ("Signalling state change to: %d", newstate); remote_logon_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, RemoteLogon *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 (RemoteLogon RLS_UNUSED *rl, GDBusMethodInvocation * invocation, gpointer RLS_UNUSED 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_debug ("handle_get_servers: returning %s", g_variant_print (array, FALSE)); 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 (RemoteLogon RLS_UNUSED *rl, GDBusMethodInvocation *invocation, gpointer RLS_UNUSED 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 (RemoteLogon RLS_UNUSED *rl, GDBusMethodInvocation *invocation, gpointer RLS_UNUSED 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 (RemoteLogon RLS_UNUSED * rl, GDBusMethodInvocation * invocation, gpointer RLS_UNUSED 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 RLS_UNUSED * 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 logon service. Defaults to '/etc/remote-logon-service.conf'."), N_("key_file")}, {NULL} }; int main (int argc, char * argv[]) { GError * error = NULL; #if !GLIB_CHECK_VERSION (2, 35, 1) /* Init the GTypes */ g_type_init(); #endif /* 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-logon-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 */ RemoteLogon * skel = remote_logon_skeleton_new(); /* Export it */ g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(skel), session_bus, "/org/ArcticaProject/RemoteLogon", 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, "org.ArcticaProject.RemoteLogon", 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); if(!gcry_check_version(NULL)) { return -1; } gcry_control(GCRYCTL_DISABLE_SECMEM, 0); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); /* Loop forever */ g_main_loop_run(mainloop); g_main_loop_unref(mainloop); g_object_unref(config); g_free(cmnd_line_config); return 0; }