/*
* 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
/* 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 * 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 * 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_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 * 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 (RemoteLogon * 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 (RemoteLogon * 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 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);
/* Loop forever */
g_main_loop_run(mainloop);
g_main_loop_unref(mainloop);
g_object_unref(config);
g_free(cmnd_line_config);
return 0;
}