summaryrefslogtreecommitdiff
path: root/remote
diff options
context:
space:
mode:
authorLeandro A. F. Pereira <leandro@hardinfo.org>2010-05-03 09:27:26 -0300
committerLeandro A. F. Pereira <leandro@hardinfo.org>2010-05-03 21:08:06 -0300
commit9273c075a2f993c5154614b70233d8f74515c851 (patch)
treeeb72a8c58e6bc8f4ca3b739d28fbecc269c0052d /remote
parent9a50155ec3e27aa6cedf3f118196f1947c769a29 (diff)
Move files from hardinfo2 to root.
Diffstat (limited to 'remote')
-rw-r--r--remote/remote.c1307
-rw-r--r--remote/ssh-conn.c338
-rw-r--r--remote/xmlrpc-client.c247
-rw-r--r--remote/xmlrpc-server.c782
4 files changed, 2674 insertions, 0 deletions
diff --git a/remote/remote.c b/remote/remote.c
new file mode 100644
index 00000000..d266735d
--- /dev/null
+++ b/remote/remote.c
@@ -0,0 +1,1307 @@
+/*
+ * Remote Client
+ * HardInfo - Displays System Information
+ * Copyright (C) 2003-2009 Leandro A. F. Pereira <leandro@hardinfo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "config.h"
+
+#ifdef HAS_LIBSOUP
+
+#include "shell.h"
+#include "callbacks.h"
+#include "iconcache.h"
+#include "hardinfo.h"
+#include "xmlrpc-client.h"
+#include "ssh-conn.h"
+
+#define XMLRPC_SERVER_VERSION 1
+
+/*
+ * TODO
+ *
+ * - Add hi_deinit() to modules (so they can free up their memory)
+ * - Import / Export host list
+ * - Detect machines on local network that runs SSH
+ * - IP range scan
+ * - mDNS
+ * - Allow the user to add/remove/edit a machine
+ * - Use ~/.ssh/known_hosts as a starting point?
+ * - Different icons for different machines?
+ * - Make sure SSH can do port forwarding
+ * - Make sure the remote host has HardInfo installed
+ * - Generate a random username/password to be passed to the XML-RPC server; use
+ * that username/password on the client; this is just to make sure nobody on the
+ * machine will be allowed to obtain information that might be sensitive. This
+ * will be passed with base64, so it can be sniffed; this needs root access anyway,
+ * so not really a problem.
+ * - Determine if we're gonna use GIOChannels or create a communication thread
+ * - Introduce a flag on the modules, stating their ability to be used locally/remotely
+ * (Benchmarks can't be used remotely; Displays won't work remotely [unless we use
+ * X forwarding, but that'll be local X11 info anyway]).
+ */
+
+typedef struct _HostManager HostManager;
+typedef struct _HostDialog HostDialog;
+
+typedef enum {
+ HOST_DIALOG_MODE_EDIT,
+ HOST_DIALOG_MODE_CONNECT
+} HostDialogMode;
+
+struct _HostManager {
+ GtkWidget *dialog;
+ GtkWidget *btn_connect, *btn_cancel;
+ GtkWidget *btn_add, *btn_edit, *btn_remove;
+
+ GtkListStore *tree_store;
+
+ gint selected_id;
+ gchar *selected_name;
+ GtkTreeIter *selected_iter;
+};
+
+struct _HostDialog {
+ GtkWidget *dialog;
+ GtkWidget *notebook;
+
+ GtkWidget *txt_hostname, *txt_port;
+ GtkWidget *txt_ssh_user, *txt_ssh_password;
+
+ GtkWidget *cmb_type;
+
+ GtkWidget *frm_options;
+ GtkWidget *btn_accept, *btn_cancel;
+};
+
+static HostManager *host_manager_new(GtkWidget * parent);
+static void host_manager_destroy(HostManager * rd);
+static HostDialog *host_dialog_new(GtkWidget * parent,
+ gchar * title, HostDialogMode mode);
+static void host_dialog_destroy(HostDialog * hd);
+
+
+static gchar *xmlrpc_server_uri = NULL;
+static SSHConn *ssh_conn = NULL;
+
+void remote_disconnect_all(gboolean ssh)
+{
+ if (ssh && ssh_conn) {
+ ssh_close(ssh_conn);
+ ssh_conn = NULL;
+ }
+
+ if (xmlrpc_server_uri) {
+ g_free(xmlrpc_server_uri);
+ xmlrpc_server_uri = NULL;
+ }
+}
+
+static void remote_connection_error(void)
+{
+ GtkWidget *dialog;
+ static gboolean showing_error = FALSE;
+
+ if (showing_error || !xmlrpc_server_uri) {
+ return;
+ }
+
+ showing_error = TRUE;
+
+ dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_NONE,
+ "Connection to %s was lost.", xmlrpc_server_uri);
+
+ gtk_dialog_add_buttons(GTK_DIALOG(dialog),
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
+
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+ remote_disconnect_all(ssh_conn != NULL);
+ cb_local_computer();
+ }
+
+ gtk_widget_destroy(dialog);
+
+ showing_error = FALSE;
+}
+
+static gboolean remote_version_is_supported(void)
+{
+ gint remote_ver;
+ GtkWidget *dialog;
+
+ shell_status_update("Obtaining remote server API version...");
+ remote_ver =
+ xmlrpc_get_integer(xmlrpc_server_uri,
+ "server.getAPIVersion", NULL);
+
+ switch (remote_ver) {
+ case -1:
+ dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_NONE,
+ "Remote host didn't respond. Try again?");
+
+ gtk_dialog_add_buttons(GTK_DIALOG(dialog),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ "Try again", GTK_RESPONSE_ACCEPT, NULL);
+
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+ gtk_widget_destroy(dialog);
+ return remote_version_is_supported();
+ }
+
+ gtk_widget_destroy(dialog);
+ break;
+ case XMLRPC_SERVER_VERSION:
+ return TRUE;
+ default:
+ dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "Remote Host has an unsupported "
+ "API version (%d). Expected "
+ "version is %d.",
+ remote_ver, XMLRPC_SERVER_VERSION);
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ }
+
+ return FALSE;
+}
+
+static gchar *remote_module_entry_func()
+{
+ Shell *shell = shell_get_main_shell();
+ gchar *ret;
+
+ ret =
+ xmlrpc_get_string(xmlrpc_server_uri,
+ "module.entryFunction", "%s%i",
+ shell->selected_module->name,
+ shell->selected->number);
+
+ if (!ret) {
+ ret = g_strdup("");
+ }
+
+ return ret;
+}
+
+static void remote_module_entry_scan_func(gboolean reload)
+{
+ Shell *shell = shell_get_main_shell();
+
+ if (reload) {
+ xmlrpc_get_string(xmlrpc_server_uri,
+ "module.entryReload", "%s%i",
+ shell->selected_module->name,
+ shell->selected->number);
+ } else {
+ xmlrpc_get_string(xmlrpc_server_uri,
+ "module.entryScan", "%s%i",
+ shell->selected_module->name,
+ shell->selected->number);
+ }
+}
+
+static gchar *remote_module_entry_field_func(gchar * entry)
+{
+ Shell *shell = shell_get_main_shell();
+ gchar *ret;
+
+ ret =
+ xmlrpc_get_string(xmlrpc_server_uri,
+ "module.entryGetField", "%s%i%s",
+ shell->selected_module->name,
+ shell->selected->number, entry);
+
+ if (!ret) {
+ remote_connection_error();
+ }
+
+ return ret;
+}
+
+static gchar *remote_module_entry_more_func(gchar * entry)
+{
+ Shell *shell = shell_get_main_shell();
+ gchar *ret;
+
+ ret =
+ xmlrpc_get_string(xmlrpc_server_uri,
+ "module.entryGetMoreInfo", "%s%i%s",
+ shell->selected_module->name,
+ shell->selected->number, entry);
+
+ if (!ret) {
+ remote_connection_error();
+ }
+
+ return ret;
+}
+
+static gchar *remote_module_entry_note_func(gint entry)
+{
+ Shell *shell = shell_get_main_shell();
+ gchar *note;
+
+ note =
+ xmlrpc_get_string(xmlrpc_server_uri,
+ "module.entryGetNote", "%s%i",
+ shell->selected_module->name,
+ shell->selected->number);
+
+ if (note && *note == '\0') {
+ g_free(note);
+ return NULL;
+ }
+
+ return note;
+}
+
+static ModuleAbout *remote_module_get_about()
+{
+ return NULL;
+}
+
+static gboolean load_module_list()
+{
+ Shell *shell;
+ GValueArray *modules;
+ int i = 0;
+
+ shell_status_update("Unloading local modules...");
+ module_unload_all();
+
+ shell_status_update("Obtaining remote server module list...");
+ modules =
+ xmlrpc_get_array(xmlrpc_server_uri, "module.getModuleList", NULL);
+ if (!modules) {
+ return FALSE;
+ }
+
+ shell = shell_get_main_shell();
+
+ for (; i < modules->n_values; i++) {
+ ShellModule *m;
+ ShellModuleEntry *e;
+ GValueArray *entries, *module;
+ int j = 0;
+
+ module = g_value_get_boxed(&modules->values[i]);
+
+ m = g_new0(ShellModule, 1);
+ m->name = g_strdup(g_value_get_string(&module->values[0]));
+ m->icon =
+ icon_cache_get_pixbuf(g_value_get_string(&module->values[1]));
+ m->aboutfunc = (gpointer) remote_module_get_about;
+
+ shell_status_pulse();
+ entries = xmlrpc_get_array(xmlrpc_server_uri,
+ "module.getEntryList", "%s", m->name);
+ if (entries && entries->n_values > 0) {
+ for (; j < entries->n_values; j++) {
+ GValueArray *tuple =
+ g_value_get_boxed(&entries->values[j]);
+
+ e = g_new0(ShellModuleEntry, 1);
+ e->name = g_strdup(g_value_get_string(&tuple->values[0]));
+ e->icon =
+ icon_cache_get_pixbuf(g_value_get_string
+ (&tuple->values[1]));
+ e->icon_file =
+ g_strdup(g_value_get_string(&tuple->values[1]));
+ e->number = j;
+
+ e->func = remote_module_entry_func;
+ e->scan_func = remote_module_entry_scan_func;
+ e->fieldfunc = remote_module_entry_field_func;
+ e->morefunc = remote_module_entry_more_func;
+ e->notefunc = remote_module_entry_note_func;
+
+ m->entries = g_slist_append(m->entries, e);
+
+ shell_status_pulse();
+ }
+
+ g_value_array_free(entries);
+
+ shell->tree->modules = g_slist_append(shell->tree->modules, m);
+ } else {
+ g_free(m->name);
+ g_free(m);
+ }
+ }
+
+ g_slist_foreach(shell->tree->modules, shell_add_modules_to_gui,
+ shell->tree);
+ gtk_tree_view_expand_all(GTK_TREE_VIEW(shell->tree->view));
+
+ g_value_array_free(modules);
+
+ return TRUE;
+}
+
+static gboolean remote_connect_direct(gchar * hostname, gint port)
+{
+ gboolean retval = FALSE;
+
+ remote_disconnect_all(FALSE);
+
+ xmlrpc_init();
+ xmlrpc_server_uri =
+ g_strdup_printf("http://%s:%d/xmlrpc", hostname, port);
+
+ shell_view_set_enabled(FALSE);
+
+ if (remote_version_is_supported()) {
+ if (!load_module_list()) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "Cannot obtain module list from server.");
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ } else {
+ retval = TRUE;
+ }
+ }
+
+ shell_status_update("Done.");
+ shell_view_set_enabled(TRUE);
+
+ return retval;
+}
+
+static gboolean remote_connect_ssh(gchar * hostname,
+ gint port,
+ gchar * username, gchar * password)
+{
+ GtkWidget *dialog;
+ SSHConnResponse ssh_response;
+ SoupURI *uri;
+ gchar *struri;
+ char buffer[32];
+ gboolean error = FALSE;
+
+ remote_disconnect_all(TRUE);
+
+ shell_view_set_enabled(FALSE);
+ shell_status_update("Establishing SSH tunnel...");
+ struri = g_strdup_printf("ssh://%s:%s@%s:%d/?L4343:localhost:4242",
+ username, password, hostname, port);
+ uri = soup_uri_new(struri);
+ ssh_response = ssh_new(uri, &ssh_conn, "hardinfo -x");
+
+ if (ssh_response != SSH_CONN_OK) {
+ error = TRUE;
+ ssh_close(ssh_conn);
+ } else {
+ gint res;
+ gint bytes_read;
+
+ memset(buffer, 0, sizeof(buffer));
+ res =
+ ssh_read(ssh_conn->fd_read, buffer, sizeof(buffer),
+ &bytes_read);
+ if (bytes_read != 0 && res == 1) {
+ if (strncmp(buffer, "XML-RPC server ready", 20) == 0) {
+ DEBUG("%s", buffer);
+
+ if (remote_connect_direct("127.0.0.1", 4343)) {
+ DEBUG("connected! :)");
+ goto out;
+ }
+
+ DEBUG("unknown error while trying to connect... wtf?");
+ }
+
+ /* TODO FIXME Perhaps the server is already running; try to fix */
+ DEBUG("hardinfo already running there?");
+ }
+
+ error = TRUE;
+ }
+
+ out:
+ if (error) {
+ dialog = gtk_message_dialog_new_with_markup(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "<b><big>Cannot establish tunnel.</big></b>\n"
+ "<i>%s</i>\n\n"
+ "Please verify that:\n"
+ "\342\200\242 The hostname <b>%s</b> is correct;\n"
+ "\342\200\242 There is a SSH server running on port <b>%d</b>;\n"
+ "\342\200\242 Your username/password combination is correct.",
+ ssh_response ==
+ SSH_CONN_OK ? "" :
+ ssh_conn_errors
+ [ssh_response],
+ hostname, port);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+
+ ssh_close(ssh_conn);
+ ssh_conn = NULL;
+ }
+
+ soup_uri_free(uri);
+ g_free(struri);
+ shell_view_set_enabled(TRUE);
+ shell_status_update("Done.");
+
+ return !error;
+}
+
+gboolean remote_connect_host(gchar * hostname)
+{
+ Shell *shell = shell_get_main_shell();
+ gboolean retval = FALSE;
+
+ if (!g_key_file_has_group(shell->hosts, hostname)) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "Internal error.");
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ } else {
+ const gint port =
+ g_key_file_get_integer(shell->hosts, hostname, "port", NULL);
+ gchar *type =
+ g_key_file_get_string(shell->hosts, hostname, "type", NULL);
+
+ if (g_str_equal(type, "ssh")) {
+ gchar *username =
+ g_key_file_get_string(shell->hosts, hostname, "username",
+ NULL);
+ gchar *password =
+ g_key_file_get_string(shell->hosts, hostname, "password",
+ NULL);
+
+ retval =
+ remote_connect_ssh(hostname, port, username, password);
+
+ g_free(username);
+ g_free(password);
+ } else {
+ retval = remote_connect_direct(hostname, port);
+ }
+
+ g_free(type);
+ }
+
+ return retval;
+}
+
+void connect_dialog_show(GtkWidget * parent)
+{
+ HostDialog *he =
+ host_dialog_new(parent, "Connect to", HOST_DIALOG_MODE_CONNECT);
+
+ if (gtk_dialog_run(GTK_DIALOG(he->dialog)) == GTK_RESPONSE_ACCEPT) {
+ gboolean connected;
+ gchar *hostname =
+ (gchar *) gtk_entry_get_text(GTK_ENTRY(he->txt_hostname));
+ const gint selected_type =
+ gtk_combo_box_get_active(GTK_COMBO_BOX(he->cmb_type));
+ const gint port =
+ (int) gtk_spin_button_get_value(GTK_SPIN_BUTTON(he->txt_port));
+
+ gtk_widget_set_sensitive(he->dialog, FALSE);
+
+ if (selected_type == 1) {
+ gchar *username =
+ (gchar *) gtk_entry_get_text(GTK_ENTRY(he->txt_ssh_user));
+ gchar *password =
+ (gchar *)
+ gtk_entry_get_text(GTK_ENTRY(he->txt_ssh_password));
+
+ connected =
+ remote_connect_ssh(hostname, port, username, password);
+ } else {
+ connected = remote_connect_direct(hostname, port);
+ }
+
+ if (connected) {
+ Shell *shell = shell_get_main_shell();
+ gchar *tmp;
+
+ tmp = g_strdup_printf("Remote: <b>%s</b>", hostname);
+ shell_set_remote_label(shell, tmp);
+
+ g_free(tmp);
+ } else {
+ cb_local_computer();
+ }
+ }
+
+ host_dialog_destroy(he);
+}
+
+void host_manager_show(GtkWidget * parent)
+{
+ HostManager *rd = host_manager_new(parent);
+
+ gtk_dialog_run(GTK_DIALOG(rd->dialog));
+
+ host_manager_destroy(rd);
+}
+
+static void populate_store(HostManager * rd, GtkListStore * store)
+{
+ Shell *shell;
+ GtkTreeIter iter;
+ gchar **hosts;
+ gint i;
+ gsize no_groups;
+
+ gtk_list_store_clear(store);
+ shell = shell_get_main_shell();
+
+ hosts = g_key_file_get_groups(shell->hosts, &no_groups);
+ DEBUG("%d hosts found", no_groups);
+ for (i = 0; i < no_groups; i++) {
+ gchar *icon;
+
+ DEBUG("host #%d: %s", i, hosts[i]);
+
+ icon = g_key_file_get_string(shell->hosts, hosts[i], "icon", NULL);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter,
+ 0,
+ icon_cache_get_pixbuf(icon ? icon :
+ "server.png"), 1,
+ g_strdup(hosts[i]), 2, GINT_TO_POINTER(i), -1);
+
+ g_free(icon);
+ }
+
+ g_strfreev(hosts);
+}
+
+static GtkTreeModel *host_dialog_get_completion_model(void)
+{
+ Shell *shell;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gchar **groups;
+ gint i = 0;
+
+ shell = shell_get_main_shell();
+
+ store = gtk_list_store_new(1, G_TYPE_STRING);
+ for (groups = g_key_file_get_groups(shell->hosts, NULL); groups[i];
+ i++) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, g_strdup(groups[i]), -1);
+ }
+ g_strfreev(groups);
+
+ return GTK_TREE_MODEL(store);
+}
+
+static void host_combo_changed_cb(GtkComboBox * widget, gpointer user_data)
+{
+ HostDialog *host_dlg = (HostDialog *) user_data;
+ const gint default_ports[] = { 4242, 22 };
+ gint index;
+
+ index = gtk_combo_box_get_active(widget);
+
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(host_dlg->notebook), index);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(host_dlg->txt_port),
+ default_ports[index]);
+
+ if (index == 0) {
+ gtk_widget_hide(host_dlg->frm_options);
+ } else {
+ gtk_widget_show(host_dlg->frm_options);
+ }
+}
+
+static void
+host_dialog_hostname_changed (GtkEditable *entry, gpointer user_data)
+{
+ HostDialog *host_dlg = (HostDialog *)user_data;
+ GRegex *regex_ip = NULL, *regex_host = NULL;
+ gboolean match;
+ const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry));
+
+ /*
+ * Regexes from:
+ * http://stackoverflow.com/questions/106179
+ */
+ const gchar *valid_ip_regex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|" \
+ "25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}" \
+ "|2[0-4][0-9]|25[0-5])$";
+ const gchar *valid_hostname_regex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9" \
+ "\\-]*[a-zA-Z0-9])\\.)*([A-Za-z]|[A-Z" \
+ "a-z][A-Za-z0-9\\-]*[A-Za-z0-9])$";
+ if (regex_ip == NULL) {
+ regex_ip = g_regex_new(valid_ip_regex, 0, 0, NULL);
+ }
+
+ if (regex_host == NULL) {
+ regex_host = g_regex_new(valid_hostname_regex, 0, 0, NULL);
+ }
+
+ match = g_regex_match(regex_ip, text, 0, NULL);
+ if (!match) {
+ match = g_regex_match(regex_host, text, 0, NULL);
+ }
+
+ gtk_widget_set_sensitive(host_dlg->btn_accept, match);
+}
+
+static void host_dialog_destroy(HostDialog * rd)
+{
+ gtk_widget_destroy(rd->dialog);
+ g_free(rd);
+}
+
+static HostDialog *host_dialog_new(GtkWidget * parent,
+ gchar * title, HostDialogMode mode)
+{
+ HostDialog *host_dlg;
+ GtkWidget *dialog;
+ GtkWidget *dialog_vbox1;
+ GtkWidget *vbox1;
+ GtkWidget *frm_remote_host;
+ GtkWidget *alignment1;
+ GtkWidget *table1;
+ GtkWidget *label2;
+ GtkWidget *label3;
+ GtkWidget *label4;
+ GtkWidget *cmb_type;
+ GtkWidget *txt_hostname;
+ GtkWidget *alignment2;
+ GtkWidget *hbox1;
+ GtkObject *txt_port_adj;
+ GtkWidget *txt_port;
+ GtkWidget *label1;
+ GtkWidget *frm_options;
+ GtkWidget *alignment3;
+ GtkWidget *notebook;
+ GtkWidget *table2;
+ GtkWidget *label8;
+ GtkWidget *label9;
+ GtkWidget *txt_ssh_user;
+ GtkWidget *txt_ssh_password;
+ GtkWidget *label10;
+ GtkWidget *label5;
+ GtkWidget *dialog_action_area1;
+ GtkWidget *btn_cancel;
+ GtkWidget *btn_save;
+ GtkEntryCompletion *completion;
+ GtkTreeModel *completion_model;
+
+ dialog = gtk_dialog_new();
+ gtk_window_set_title(GTK_WINDOW(dialog), title);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent));
+ gtk_window_set_position(GTK_WINDOW(dialog),
+ GTK_WIN_POS_CENTER_ON_PARENT);
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_type_hint(GTK_WINDOW(dialog),
+ GDK_WINDOW_TYPE_HINT_DIALOG);
+ gtk_window_set_gravity(GTK_WINDOW(dialog), GDK_GRAVITY_CENTER);
+
+ dialog_vbox1 = GTK_DIALOG(dialog)->vbox;
+ gtk_widget_show(dialog_vbox1);
+
+ vbox1 = gtk_vbox_new(FALSE, 3);
+ gtk_widget_show(vbox1);
+ gtk_box_pack_start(GTK_BOX(dialog_vbox1), vbox1, TRUE, TRUE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox1), 5);
+ gtk_box_set_spacing(GTK_BOX(vbox1), 10);
+
+ frm_remote_host = gtk_frame_new(NULL);
+ gtk_widget_show(frm_remote_host);
+ gtk_box_pack_start(GTK_BOX(vbox1), frm_remote_host, FALSE, TRUE, 0);
+ gtk_frame_set_shadow_type(GTK_FRAME(frm_remote_host), GTK_SHADOW_NONE);
+
+ alignment1 = gtk_alignment_new(0.5, 0.5, 1, 1);
+ gtk_widget_show(alignment1);
+ gtk_container_add(GTK_CONTAINER(frm_remote_host), alignment1);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(alignment1), 0, 0, 12, 0);
+
+ table1 = gtk_table_new(3, 2, FALSE);
+ gtk_widget_show(table1);
+ gtk_container_add(GTK_CONTAINER(alignment1), table1);
+ gtk_table_set_row_spacings(GTK_TABLE(table1), 4);
+ gtk_table_set_col_spacings(GTK_TABLE(table1), 4);
+
+ label2 = gtk_label_new("Protocol:");
+ gtk_widget_show(label2);
+ gtk_table_attach(GTK_TABLE(table1), label2, 0, 1, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
+
+ label3 = gtk_label_new("Host:");
+ gtk_widget_show(label3);
+ gtk_table_attach(GTK_TABLE(table1), label3, 0, 1, 1, 2,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment(GTK_MISC(label3), 0, 0.5);
+
+ label4 = gtk_label_new("Port:");
+ gtk_widget_show(label4);
+ gtk_table_attach(GTK_TABLE(table1), label4, 0, 1, 2, 3,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment(GTK_MISC(label4), 0, 0.5);
+
+ cmb_type = gtk_combo_box_new_text();
+ gtk_widget_show(cmb_type);
+ gtk_table_attach(GTK_TABLE(table1), cmb_type, 1, 2, 0, 1,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+ gtk_combo_box_append_text(GTK_COMBO_BOX(cmb_type),
+ "Direct connection");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(cmb_type),
+ "Remote tunnel over SSH");
+
+ txt_hostname = gtk_entry_new();
+ gtk_widget_show(txt_hostname);
+ gtk_table_attach(GTK_TABLE(table1), txt_hostname, 1, 2, 1, 2,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+
+ alignment2 = gtk_alignment_new(0.5, 0.5, 1, 1);
+ gtk_widget_show(alignment2);
+ gtk_table_attach(GTK_TABLE(table1), alignment2, 1, 2, 2, 3,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+
+ hbox1 = gtk_hbox_new(FALSE, 0);
+ gtk_widget_show(hbox1);
+ gtk_container_add(GTK_CONTAINER(alignment2), hbox1);
+
+ txt_port_adj = gtk_adjustment_new(4242, 1, 65535, 1, 10, 0);
+ txt_port = gtk_spin_button_new(GTK_ADJUSTMENT(txt_port_adj), 1, 0);
+ gtk_widget_show(txt_port);
+ gtk_box_pack_start(GTK_BOX(hbox1), txt_port, FALSE, TRUE, 0);
+
+ label1 = gtk_label_new("<b>Remote host</b>");
+ gtk_widget_show(label1);
+ gtk_frame_set_label_widget(GTK_FRAME(frm_remote_host), label1);
+ gtk_label_set_use_markup(GTK_LABEL(label1), TRUE);
+
+ frm_options = gtk_frame_new(NULL);
+ gtk_widget_show(frm_options);
+ gtk_box_pack_start(GTK_BOX(vbox1), frm_options, FALSE, TRUE, 0);
+ gtk_frame_set_shadow_type(GTK_FRAME(frm_options), GTK_SHADOW_NONE);
+
+ alignment3 = gtk_alignment_new(0.5, 0.5, 1, 1);
+ gtk_widget_show(alignment3);
+ gtk_container_add(GTK_CONTAINER(frm_options), alignment3);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(alignment3), 0, 0, 12, 0);
+
+ notebook = gtk_notebook_new();
+ gtk_widget_show(notebook);
+ gtk_container_add(GTK_CONTAINER(alignment3), notebook);
+ GTK_WIDGET_UNSET_FLAGS(notebook, GTK_CAN_FOCUS);
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
+ gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
+
+ label10 = gtk_label_new("");
+ gtk_widget_show(label10);
+ gtk_container_add(GTK_CONTAINER(notebook), label10);
+ gtk_label_set_use_markup(GTK_LABEL(label10), TRUE);
+
+ table2 = gtk_table_new(2, 2, FALSE);
+ gtk_widget_show(table2);
+ gtk_container_add(GTK_CONTAINER(notebook), table2);
+ gtk_table_set_row_spacings(GTK_TABLE(table2), 4);
+ gtk_table_set_col_spacings(GTK_TABLE(table2), 4);
+
+ label8 = gtk_label_new("User:");
+ gtk_widget_show(label8);
+ gtk_table_attach(GTK_TABLE(table2), label8, 0, 1, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment(GTK_MISC(label8), 0, 0.5);
+
+ label9 = gtk_label_new("Password:");
+ gtk_widget_show(label9);
+ gtk_table_attach(GTK_TABLE(table2), label9, 0, 1, 1, 2,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_misc_set_alignment(GTK_MISC(label9), 0, 0.5);
+
+ txt_ssh_user = gtk_entry_new();
+ gtk_widget_show(txt_ssh_user);
+ gtk_table_attach(GTK_TABLE(table2), txt_ssh_user, 1, 2, 0, 1,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+
+ txt_ssh_password = gtk_entry_new();
+ gtk_widget_show(txt_ssh_password);
+ gtk_table_attach(GTK_TABLE(table2), txt_ssh_password, 1, 2, 1, 2,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (0), 0, 0);
+ gtk_entry_set_invisible_char(GTK_ENTRY(txt_ssh_password), 9679);
+ gtk_entry_set_visibility(GTK_ENTRY(txt_ssh_password), FALSE);
+
+ label5 = gtk_label_new("<b>Connection options</b>");
+ gtk_widget_show(label5);
+ gtk_frame_set_label_widget(GTK_FRAME(frm_options), label5);
+ gtk_label_set_use_markup(GTK_LABEL(label5), TRUE);
+
+ dialog_action_area1 = GTK_DIALOG(dialog)->action_area;
+ gtk_widget_show(dialog_action_area1);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(dialog_action_area1),
+ GTK_BUTTONBOX_END);
+
+ btn_cancel = gtk_button_new_from_stock("gtk-cancel");
+ gtk_widget_show(btn_cancel);
+ gtk_dialog_add_action_widget(GTK_DIALOG(dialog), btn_cancel,
+ GTK_RESPONSE_CANCEL);
+ GTK_WIDGET_SET_FLAGS(btn_cancel, GTK_CAN_DEFAULT);
+
+ if (mode == HOST_DIALOG_MODE_EDIT) {
+ btn_save = gtk_button_new_from_stock(GTK_STOCK_SAVE);
+ } else if (mode == HOST_DIALOG_MODE_CONNECT) {
+ btn_save = gtk_button_new_from_stock(GTK_STOCK_CONNECT);
+ }
+
+ gtk_widget_show(btn_save);
+ gtk_dialog_add_action_widget(GTK_DIALOG(dialog), btn_save,
+ GTK_RESPONSE_ACCEPT);
+ GTK_WIDGET_SET_FLAGS(btn_save, GTK_CAN_DEFAULT);
+
+ host_dlg = g_new0(HostDialog, 1);
+ host_dlg->dialog = dialog;
+ host_dlg->notebook = notebook;
+ host_dlg->txt_hostname = txt_hostname;
+ host_dlg->txt_port = txt_port;
+ host_dlg->txt_ssh_user = txt_ssh_user;
+ host_dlg->txt_ssh_password = txt_ssh_password;
+ host_dlg->cmb_type = cmb_type;
+ host_dlg->frm_options = frm_options;
+ host_dlg->btn_accept = btn_save;
+ host_dlg->btn_cancel = btn_cancel;
+
+ completion = gtk_entry_completion_new();
+ gtk_entry_set_completion(GTK_ENTRY(host_dlg->txt_hostname), completion);
+ g_object_unref(completion);
+
+ completion_model = host_dialog_get_completion_model();
+ gtk_entry_completion_set_model(completion, completion_model);
+ g_object_unref(completion_model);
+
+ gtk_entry_completion_set_text_column(completion, 0);
+
+ gtk_combo_box_set_active(GTK_COMBO_BOX(host_dlg->cmb_type), 0);
+
+ g_signal_connect(G_OBJECT(txt_hostname), "changed",
+ G_CALLBACK(host_dialog_hostname_changed), host_dlg);
+ g_signal_connect(G_OBJECT(cmb_type), "changed",
+ G_CALLBACK(host_combo_changed_cb), host_dlg);
+
+ host_combo_changed_cb(GTK_COMBO_BOX(cmb_type), host_dlg);
+ host_dialog_hostname_changed(GTK_EDITABLE(txt_hostname), host_dlg);
+
+ gtk_entry_set_activates_default(GTK_ENTRY(txt_hostname), TRUE);
+
+ return host_dlg;
+}
+
+static void host_manager_add(GtkWidget * button, gpointer data)
+{
+ Shell *shell = shell_get_main_shell();
+ HostManager *rd = (HostManager *) data;
+ HostDialog *he =
+ host_dialog_new(rd->dialog, "Add a host", HOST_DIALOG_MODE_EDIT);
+
+ retry:
+ if (gtk_dialog_run(GTK_DIALOG(he->dialog)) == GTK_RESPONSE_ACCEPT) {
+ const gchar *hostname =
+ gtk_entry_get_text(GTK_ENTRY(he->txt_hostname));
+
+ if (g_key_file_has_group(shell->hosts, hostname)) {
+ GtkWidget *dialog;
+
+ dialog =
+ gtk_message_dialog_new_with_markup(GTK_WINDOW(rd->dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "Hostname <b>%s</b> already exists.",
+ hostname);
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+
+ goto retry;
+ } else {
+ GtkTreeIter iter;
+ const gchar *type[] = { "direct", "ssh" };
+ const gint selected_type =
+ gtk_combo_box_get_active(GTK_COMBO_BOX(he->cmb_type));
+ const gint port =
+ (int)
+ gtk_spin_button_get_value(GTK_SPIN_BUTTON(he->txt_port));
+
+ g_key_file_set_string(shell->hosts, hostname, "type",
+ type[selected_type]);
+ g_key_file_set_integer(shell->hosts, hostname, "port", port);
+
+ if (selected_type == 1) {
+ const gchar *username =
+ gtk_entry_get_text(GTK_ENTRY(he->txt_ssh_user));
+ const gchar *password =
+ gtk_entry_get_text(GTK_ENTRY(he->txt_ssh_password));
+
+ g_key_file_set_string(shell->hosts, hostname, "username",
+ username);
+ g_key_file_set_string(shell->hosts, hostname, "password",
+ password);
+ }
+
+ gtk_list_store_append(rd->tree_store, &iter);
+ gtk_list_store_set(rd->tree_store, &iter,
+ 0, icon_cache_get_pixbuf("server.png"),
+ 1, g_strdup(hostname), 2, 0, -1);
+ }
+ }
+
+ host_dialog_destroy(he);
+}
+
+static void host_manager_edit(GtkWidget * button, gpointer data)
+{
+ Shell *shell = shell_get_main_shell();
+ GtkWidget *dialog;
+ HostManager *rd = (HostManager *) data;
+ HostDialog *he =
+ host_dialog_new(rd->dialog, "Edit a host", HOST_DIALOG_MODE_EDIT);
+ gchar *host_type;
+ gchar *previous_hostname;
+ gint host_port;
+
+ host_type =
+ g_key_file_get_string(shell->hosts, rd->selected_name, "type",
+ NULL);
+ if (!host_type || g_str_equal(host_type, "direct")) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(he->cmb_type), 0);
+ } else if (g_str_equal(host_type, "ssh")) {
+ gchar *username, *password;
+
+ gtk_combo_box_set_active(GTK_COMBO_BOX(he->cmb_type), 1);
+
+ username =
+ g_key_file_get_string(shell->hosts, rd->selected_name,
+ "username", NULL);
+ if (username) {
+ gtk_entry_set_text(GTK_ENTRY(he->txt_ssh_user), username);
+ g_free(username);
+ }
+
+ password =
+ g_key_file_get_string(shell->hosts, rd->selected_name,
+ "password", NULL);
+ if (password) {
+ gtk_entry_set_text(GTK_ENTRY(he->txt_ssh_password), password);
+ g_free(password);
+ }
+ } else {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(rd->dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "Host has invalid type(%s).",
+ host_type);
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+
+ goto bad;
+ }
+
+ gtk_entry_set_text(GTK_ENTRY(he->txt_hostname), rd->selected_name);
+ previous_hostname = rd->selected_name;
+
+ host_port =
+ g_key_file_get_integer(shell->hosts, rd->selected_name, "port",
+ NULL);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(he->txt_port),
+ host_port ? host_port : 4242);
+
+ if (gtk_dialog_run(GTK_DIALOG(he->dialog)) == GTK_RESPONSE_ACCEPT) {
+ const gchar *type[] = { "direct", "ssh" };
+ const gint selected_type =
+ gtk_combo_box_get_active(GTK_COMBO_BOX(he->cmb_type));
+ const gint port =
+ (int) gtk_spin_button_get_value(GTK_SPIN_BUTTON(he->txt_port));
+ const gchar *hostname =
+ gtk_entry_get_text(GTK_ENTRY(he->txt_hostname));
+
+ if (!g_str_equal(previous_hostname, he->txt_hostname)) {
+ g_key_file_remove_group(shell->hosts, previous_hostname, NULL);
+ gtk_list_store_set(rd->tree_store, rd->selected_iter,
+ 1, g_strdup(hostname), -1);
+ }
+
+ g_key_file_set_string(shell->hosts, hostname, "type",
+ type[selected_type]);
+ g_key_file_set_integer(shell->hosts, hostname, "port", port);
+
+ if (selected_type == 1) {
+ const gchar *username =
+ gtk_entry_get_text(GTK_ENTRY(he->txt_ssh_user));
+ const gchar *password =
+ gtk_entry_get_text(GTK_ENTRY(he->txt_ssh_password));
+
+ g_key_file_set_string(shell->hosts, hostname, "username",
+ username);
+ g_key_file_set_string(shell->hosts, hostname, "password",
+ password);
+ }
+ }
+
+ bad:
+ host_dialog_destroy(he);
+ g_free(host_type);
+}
+
+static void host_manager_remove(GtkWidget * button, gpointer data)
+{
+ Shell *shell = shell_get_main_shell();
+ HostManager *rd = (HostManager *) data;
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(rd->dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ "Remove the host <b>%s</b>?",
+ rd->selected_name);
+
+ gtk_dialog_add_buttons(GTK_DIALOG(dialog),
+ GTK_STOCK_NO, GTK_RESPONSE_REJECT,
+ GTK_STOCK_DELETE, GTK_RESPONSE_ACCEPT, NULL);
+
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+ g_key_file_remove_group(shell->hosts, rd->selected_name, NULL);
+ gtk_list_store_remove(rd->tree_store, rd->selected_iter);
+
+ gtk_widget_set_sensitive(rd->btn_edit, FALSE);
+ gtk_widget_set_sensitive(rd->btn_remove, FALSE);
+ }
+
+ gtk_widget_destroy(dialog);
+}
+
+static void host_manager_tree_sel_changed(GtkTreeSelection * sel,
+ gpointer data)
+{
+ HostManager *rd = (HostManager *) data;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
+ gchar *name;
+ gint id;
+
+ gtk_tree_model_get(model, &iter, 1, &name, 2, &id, -1);
+
+ if (id != -1) {
+ gtk_widget_set_sensitive(GTK_WIDGET(rd->btn_edit), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(rd->btn_remove), TRUE);
+ } else {
+ gtk_widget_set_sensitive(GTK_WIDGET(rd->btn_edit), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(rd->btn_remove), FALSE);
+ }
+
+ g_free(rd->selected_name);
+
+ if (rd->selected_iter)
+ gtk_tree_iter_free(rd->selected_iter);
+
+ rd->selected_id = id;
+ rd->selected_name = name;
+ rd->selected_iter = gtk_tree_iter_copy(&iter);
+ }
+}
+
+static void host_manager_destroy(HostManager * rd)
+{
+ shell_save_hosts_file();
+ shell_update_remote_menu();
+ gtk_widget_destroy(rd->dialog);
+
+ g_free(rd);
+}
+
+static HostManager *host_manager_new(GtkWidget * parent)
+{
+ HostManager *rd;
+ GtkWidget *dialog;
+ GtkWidget *dialog_vbox;
+ GtkWidget *scrolledwindow;
+ GtkWidget *treeview;
+ GtkWidget *vbuttonbox;
+ GtkWidget *btn_add;
+ GtkWidget *btn_edit;
+ GtkWidget *dialog_action_area;
+ GtkWidget *btn_cancel;
+ GtkWidget *btn_remove;
+ GtkWidget *hbox;
+ GtkTreeSelection *sel;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cr_text, *cr_pbuf;
+ GtkListStore *store;
+ GtkTreeModel *model;
+
+ rd = g_new0(HostManager, 1);
+
+ dialog = gtk_dialog_new();
+ gtk_window_set_title(GTK_WINDOW(dialog), "Remote Host Manager");
+ gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
+ gtk_window_set_default_size(GTK_WINDOW(dialog), 420, 260);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent));
+ gtk_window_set_position(GTK_WINDOW(dialog),
+ GTK_WIN_POS_CENTER_ON_PARENT);
+ gtk_window_set_type_hint(GTK_WINDOW(dialog),
+ GDK_WINDOW_TYPE_HINT_DIALOG);
+
+ dialog_vbox = GTK_DIALOG(dialog)->vbox;
+ gtk_box_set_spacing(GTK_BOX(dialog_vbox), 5);
+ gtk_container_set_border_width(GTK_CONTAINER(dialog_vbox), 4);
+ gtk_widget_show(dialog_vbox);
+
+ hbox = gtk_hbox_new(FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 0);
+ gtk_widget_show(hbox);
+
+ scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
+ gtk_widget_show(scrolledwindow);
+ gtk_box_pack_start(GTK_BOX(hbox), scrolledwindow, TRUE, TRUE, 0);
+ gtk_widget_set_size_request(scrolledwindow, -1, 200);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW
+ (scrolledwindow), GTK_SHADOW_IN);
+
+ store =
+ gtk_list_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_INT);
+ model = GTK_TREE_MODEL(store);
+
+ treeview = gtk_tree_view_new_with_model(model);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
+ gtk_widget_show(treeview);
+ gtk_container_add(GTK_CONTAINER(scrolledwindow), treeview);
+ gtk_tree_view_set_reorderable(GTK_TREE_VIEW(treeview), TRUE);
+
+ column = gtk_tree_view_column_new();
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+ cr_pbuf = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_start(column, cr_pbuf, FALSE);
+ gtk_tree_view_column_add_attribute(column, cr_pbuf, "pixbuf",
+ TREE_COL_PBUF);
+
+ cr_text = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, cr_text, TRUE);
+ gtk_tree_view_column_add_attribute(column, cr_text, "markup",
+ TREE_COL_NAME);
+
+ vbuttonbox = gtk_vbutton_box_new();
+ gtk_widget_show(vbuttonbox);
+ gtk_box_pack_start(GTK_BOX(hbox), vbuttonbox, FALSE, TRUE, 0);
+ gtk_box_set_spacing(GTK_BOX(vbuttonbox), 5);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(vbuttonbox),
+ GTK_BUTTONBOX_START);
+
+ btn_add = gtk_button_new_with_mnemonic("_Add");
+ gtk_widget_show(btn_add);
+ gtk_container_add(GTK_CONTAINER(vbuttonbox), btn_add);
+ GTK_WIDGET_SET_FLAGS(btn_add, GTK_CAN_DEFAULT);
+ g_signal_connect(btn_add, "clicked", G_CALLBACK(host_manager_add), rd);
+
+ btn_edit = gtk_button_new_with_mnemonic("_Edit");
+ gtk_widget_show(btn_edit);
+ gtk_container_add(GTK_CONTAINER(vbuttonbox), btn_edit);
+ GTK_WIDGET_SET_FLAGS(btn_edit, GTK_CAN_DEFAULT);
+ g_signal_connect(btn_edit, "clicked", G_CALLBACK(host_manager_edit),
+ rd);
+
+ btn_remove = gtk_button_new_with_mnemonic("_Remove");
+ gtk_widget_show(btn_remove);
+ gtk_container_add(GTK_CONTAINER(vbuttonbox), btn_remove);
+ GTK_WIDGET_SET_FLAGS(btn_remove, GTK_CAN_DEFAULT);
+ g_signal_connect(btn_remove, "clicked",
+ G_CALLBACK(host_manager_remove), rd);
+
+ dialog_action_area = GTK_DIALOG(dialog)->action_area;
+ gtk_widget_show(dialog_action_area);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(dialog_action_area),
+ GTK_BUTTONBOX_END);
+
+ btn_cancel = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+ gtk_widget_show(btn_cancel);
+ gtk_dialog_add_action_widget(GTK_DIALOG(dialog), btn_cancel,
+ GTK_RESPONSE_CANCEL);
+ GTK_WIDGET_SET_FLAGS(btn_cancel, GTK_CAN_DEFAULT);
+
+ gtk_tree_view_collapse_all(GTK_TREE_VIEW(treeview));
+
+ sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+
+ g_signal_connect(G_OBJECT(sel), "changed",
+ (GCallback) host_manager_tree_sel_changed, rd);
+
+ rd->dialog = dialog;
+ rd->btn_cancel = btn_cancel;
+ rd->btn_add = btn_add;
+ rd->btn_edit = btn_edit;
+ rd->btn_remove = btn_remove;
+ rd->tree_store = store;
+
+ populate_store(rd, store);
+ gtk_widget_set_sensitive(GTK_WIDGET(rd->btn_edit), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(rd->btn_remove), FALSE);
+
+ return rd;
+}
+
+#endif /* HAS_LIBSOUP */
diff --git a/remote/ssh-conn.c b/remote/ssh-conn.c
new file mode 100644
index 00000000..7f099d35
--- /dev/null
+++ b/remote/ssh-conn.c
@@ -0,0 +1,338 @@
+/*
+ Remote Client
+ HardInfo - Displays System Information
+ Copyright (C) 2003-2009 Leandro A. F. Pereira <leandro@hardinfo.org>
+
+ Based on ssh-method.c from GnomeVFS
+ Copyright (C) 1999 Free Software Foundation
+ Original author: Ian McKellar <yakk@yakk.net>
+
+ ssh-conn.c and ssh-conn.h are free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ ssh-conn.c and ssh-con.h are distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with ssh-conn.c and ssh-conn.h; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+#include "config.h"
+#ifdef HAS_LIBSOUP
+#include <glib/gstdio.h>
+#include <glib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "ssh-conn.h"
+
+const char *ssh_conn_errors[] = {
+ "OK",
+ "No URI specified",
+ "Unknown protocol",
+ "Unknown error",
+ "Cannot spawn SSH",
+ "Bad parameters",
+ "Permission denied (invalid credentials)",
+ "Host key verification failed",
+ "Connection refused",
+ "Invalid username or password"
+};
+
+int ssh_write(SSHConn * conn,
+ gconstpointer buffer, gint num_bytes, gint * bytes_written)
+{
+ int written;
+ int count = 0;
+
+ do {
+ written = write(conn->fd_write, buffer, (size_t) num_bytes);
+ if (written == -1 && errno == EINTR) {
+ count++;
+ usleep(10);
+ }
+ } while (written == -1 && errno == EINTR && count < 5);
+
+ if (written == -1) {
+ return -1;
+ }
+
+ *bytes_written = written;
+
+ return 1;
+}
+
+int ssh_read(gint fd, gpointer buffer, gint num_bytes, gint * bytes_read)
+{
+ int retval;
+ fd_set fds;
+ struct timeval tv = { 5, 0 };
+
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ *bytes_read = 0;
+
+ if ((retval = select(fd + 1, &fds, NULL, NULL, &tv)) < 0) {
+ return retval;
+ }
+
+ if ((retval = read(fd, buffer, (size_t) num_bytes)) > 0) {
+ *bytes_read = retval;
+ return 1;
+ }
+
+ return retval;
+}
+
+void ssh_close(SSHConn * conn)
+{
+ if (!conn) {
+ return;
+ }
+
+ close(conn->fd_read);
+ close(conn->fd_write);
+ close(conn->fd_error);
+
+ if (conn->uri) {
+ soup_uri_free(conn->uri);
+ }
+
+ kill(conn->pid, SIGINT);
+
+ if (conn->askpass_path) {
+ DEBUG("unlinking %s", conn->askpass_path);
+ g_remove(conn->askpass_path);
+ g_free(conn->askpass_path);
+ }
+
+ g_free(conn);
+}
+
+static void ssh_conn_setup(gpointer user_data)
+{
+ gchar *askpass_path = (gchar *) user_data;
+ int fd;
+
+ if ((fd = open("/dev/tty", O_RDWR)) != -1) {
+ ioctl(fd, TIOCNOTTY, NULL);
+ close(fd);
+ }
+
+ if (askpass_path) {
+ g_setenv("DISPLAY", "none:0.", TRUE);
+ g_setenv("SSH_ASKPASS", askpass_path, TRUE);
+ } else {
+ g_setenv("SSH_ASKPASS", "/bin/false", TRUE);
+ }
+}
+
+SSHConnResponse ssh_new(SoupURI * uri,
+ SSHConn ** conn_return, gchar * command)
+{
+ int argc, res, bytes_read;
+ char **argv, *askpass_path = NULL;
+ GString *cmd_line;
+ SSHConnResponse response;
+ SSHConn *connection;
+ gchar buffer[512];
+
+ if (!conn_return) {
+ return SSH_CONN_BAD_PARAMS;
+ }
+
+ if (!uri) {
+ return SSH_CONN_NO_URI;
+ }
+
+ if (!g_str_equal(uri->scheme, "ssh")) {
+ return SSH_CONN_UNKNOWN_PROTOCOL;
+ }
+
+ if (uri->password) {
+ int tmp_askpass;
+
+ askpass_path =
+ g_build_filename(g_get_home_dir(), ".hardinfo",
+ "ssh-askpass-XXXXXX", NULL);
+ tmp_askpass = g_mkstemp(askpass_path);
+ if (tmp_askpass > 0) {
+ gchar *tmp;
+
+ g_chmod(askpass_path, 0700);
+
+ tmp = g_strdup_printf("#!/bin/sh\n"
+ "echo '%s'\n"
+ "rm -f \"$0\"", uri->password);
+ write(tmp_askpass, tmp, strlen(tmp));
+ close(tmp_askpass);
+ g_free(tmp);
+
+ DEBUG("using [%s] as ssh-askpass", askpass_path);
+ }
+ }
+
+
+ cmd_line = g_string_new("ssh -x");
+
+ if (uri->query && strlen(uri->query)) {
+ GHashTable *query;
+ GList *keys, *key;
+
+ query = soup_form_decode(uri->query);
+ keys = g_hash_table_get_keys(query);
+
+ for (key = keys; key; key = key->next) {
+ gchar *param;
+
+ g_string_append_printf(cmd_line, " -%s", (gchar *) key->data);
+
+ if ((param = (gchar *) g_hash_table_lookup(query, key->data))) {
+ g_string_append_printf(cmd_line, "'%s'", param);
+ }
+ }
+
+ g_list_free(keys);
+ g_hash_table_destroy(query);
+ }
+
+ if (uri->user) {
+ g_string_append_printf(cmd_line, " -l '%s'", uri->user);
+ }
+
+ if (uri->port) {
+ g_string_append_printf(cmd_line, " -p %d", uri->port);
+ }
+
+ g_string_append_printf(cmd_line,
+ " %s \"LC_ALL=C %s\"", uri->host, command);
+
+ DEBUG("cmd_line = [%s]", cmd_line->str);
+
+ if (!g_shell_parse_argv(cmd_line->str, &argc, &argv, NULL)) {
+ response = SSH_CONN_BAD_PARAMS;
+ goto end;
+ }
+
+ connection = g_new0(SSHConn, 1);
+ connection->exit_status = -1;
+
+ DEBUG("spawning SSH");
+
+ if (!g_spawn_async_with_pipes(NULL, argv, NULL,
+ G_SPAWN_SEARCH_PATH,
+ ssh_conn_setup, askpass_path,
+ &connection->pid,
+ &connection->fd_write,
+ &connection->fd_read,
+ &connection->fd_error, NULL)) {
+ response = SSH_CONN_CANNOT_SPAWN_SSH;
+ goto end;
+ }
+
+ memset(buffer, 0, sizeof(buffer));
+ res = ssh_read(connection->fd_error, &buffer, sizeof(buffer),
+ &bytes_read);
+ DEBUG("bytes read: %d, result = %d", bytes_read, res);
+ if (bytes_read > 0 && res == 1) {
+ DEBUG("Received (error channel): [%s]", buffer);
+
+ if (strstr(buffer, "Permission denied")) {
+ response = SSH_CONN_PERMISSION_DENIED;
+ goto end;
+ } else if (strstr(buffer, "Host key verification failed")) {
+ response = SSH_CONN_HOST_KEY_CHECK_FAIL;
+ goto end;
+ } else if (strstr(buffer, "Connection refused")) {
+ response = SSH_CONN_REFUSED;
+ goto end;
+ }
+ }
+
+ DEBUG("no error detected; ssh conn established");
+
+ connection->uri = soup_uri_copy(uri);
+ response = SSH_CONN_OK;
+
+ end:
+ g_strfreev(argv);
+ g_string_free(cmd_line, TRUE);
+
+ if (askpass_path) {
+ if (connection) {
+ connection->askpass_path = askpass_path;
+ } else {
+ g_free(askpass_path);
+ }
+ }
+
+ if (response != SSH_CONN_OK) {
+ if (connection->uri) {
+ soup_uri_free(connection->uri);
+ }
+
+ g_free(connection);
+
+ *conn_return = NULL;
+ } else {
+ *conn_return = connection;
+ }
+
+ DEBUG("response = %d (%s)", response, ssh_conn_errors[response]);
+
+ return response;
+}
+
+#ifdef SSH_TEST
+int main(int argc, char **argv)
+{
+ SSHConn *c;
+ SSHConnResponse r;
+ SoupURI *u;
+ char buffer[256];
+
+ if (argc < 2) {
+ g_print("Usage: %s URI command\n", argv[0]);
+ g_print("Example: %s ssh://user:password@host 'ls -la /'\n",
+ argv[0]);
+ return 1;
+ }
+
+ u = soup_uri_new(argv[1]);
+ r = ssh_new(u, &c, argv[2]);
+ g_print("Connection result: %s\n", ssh_conn_errors[r]);
+
+ if (r == SSH_CONN_OK) {
+ int bytes_read;
+
+ while (ssh_read(c->fd_read, &buffer, sizeof(buffer),
+ &bytes_read) > 0) {
+ g_print("Bytes read: %d\n", bytes_read);
+ g_print("Contents: %s", buffer);
+ }
+
+ g_print("Finished running remote command\n");
+ }
+
+ g_print("Closing SSH [ptr = %p]", c);
+ ssh_close(c);
+
+ return 0;
+}
+#endif /* SSH_TEST */
+#endif /* HAS_LIBSOUP */
diff --git a/remote/xmlrpc-client.c b/remote/xmlrpc-client.c
new file mode 100644
index 00000000..d363729b
--- /dev/null
+++ b/remote/xmlrpc-client.c
@@ -0,0 +1,247 @@
+/*
+ * XMLRPC Client
+ * HardInfo - Displays System Information
+ * Copyright (C) 2003-2009 Leandro A. F. Pereira <leandro@hardinfo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <string.h>
+#include "config.h"
+#include "xmlrpc-client.h"
+
+#ifdef HAS_LIBSOUP
+static GMainLoop *loop = NULL;
+static SoupSession *session = NULL;
+static gboolean lock = FALSE;
+
+void xmlrpc_init(void)
+{
+ if (!loop) {
+ loop = g_main_loop_new(FALSE, FALSE);
+ }
+
+ if (!session) {
+ session = soup_session_async_new_with_options(SOUP_SESSION_TIMEOUT, 10, NULL);
+ }
+}
+
+static void xmlrpc_response_get_integer(SoupSession *s,
+ SoupMessage *m,
+ gpointer user_data)
+{
+ gint *response = user_data;
+
+ *response = -1;
+
+ if (SOUP_STATUS_IS_SUCCESSFUL(m->status_code)) {
+ soup_xmlrpc_extract_method_response(m->response_body->data,
+ m->response_body->length,
+ NULL,
+ G_TYPE_INT, response);
+ }
+
+ g_main_quit(loop);
+ lock = FALSE;
+}
+
+gint xmlrpc_get_integer(gchar *addr,
+ gchar *method,
+ const gchar *param_types,
+ ...)
+{
+ gint integer;
+ GValueArray *params;
+ SoupMessage *msg;
+ gchar *body;
+
+ msg = soup_message_new("POST", addr);
+
+ params = g_value_array_new(1);
+
+ if (param_types && *param_types) {
+ va_list ap;
+
+ va_start(ap, param_types);
+ while (*param_types) {
+ switch (*param_types) {
+ case '%':
+ break;
+ case 'i':
+ soup_value_array_append(params, G_TYPE_INT, va_arg(ap, int));
+ break;
+ case 's':
+ default:
+ soup_value_array_append(params, G_TYPE_STRING, va_arg(ap, char *));
+ break;
+ }
+
+ param_types++;
+ }
+
+ va_end(ap);
+ }
+
+ body = soup_xmlrpc_build_method_call(method, params->values, params->n_values);
+ g_value_array_free(params);
+
+ soup_message_set_request(msg, "text/xml",
+ SOUP_MEMORY_TAKE, body, strlen(body));
+
+ while (lock)
+ g_main_iteration(FALSE);
+
+ lock = TRUE;
+ soup_session_queue_message(session, msg, xmlrpc_response_get_integer, &integer);
+ g_main_run(loop);
+
+ return integer;
+}
+
+static void xmlrpc_response_get_string(SoupSession *s,
+ SoupMessage *m,
+ gpointer user_data)
+{
+ if (SOUP_STATUS_IS_SUCCESSFUL(m->status_code)) {
+ soup_xmlrpc_extract_method_response(m->response_body->data,
+ m->response_body->length,
+ NULL,
+ G_TYPE_STRING, user_data);
+ }
+
+ g_main_quit(loop);
+ lock = FALSE;
+}
+
+gchar *xmlrpc_get_string(gchar *addr,
+ gchar *method,
+ const gchar *param_types,
+ ...)
+{
+ GValueArray *params;
+ SoupMessage *msg;
+ gchar *body, *string = NULL;
+
+ msg = soup_message_new("POST", addr);
+
+ params = g_value_array_new(1);
+
+ if (param_types && *param_types) {
+ va_list ap;
+
+ va_start(ap, param_types);
+ while (*param_types) {
+ switch (*param_types) {
+ case '%':
+ break;
+ case 'i':
+ soup_value_array_append(params, G_TYPE_INT, va_arg(ap, int));
+ break;
+ case 's':
+ default:
+ soup_value_array_append(params, G_TYPE_STRING, va_arg(ap, char *));
+ break;
+ }
+
+ param_types++;
+ }
+
+ va_end(ap);
+ }
+
+ body = soup_xmlrpc_build_method_call(method, params->values, params->n_values);
+ g_value_array_free(params);
+
+ soup_message_set_request(msg, "text/xml",
+ SOUP_MEMORY_TAKE, body, strlen(body));
+
+ while (lock)
+ g_main_iteration(FALSE);
+
+ lock = TRUE;
+ soup_session_queue_message(session, msg, xmlrpc_response_get_string, &string);
+ g_main_run(loop);
+
+ return string;
+}
+
+static void xmlrpc_response_get_array(SoupSession *s,
+ SoupMessage *m,
+ gpointer user_data)
+{
+ if (SOUP_STATUS_IS_SUCCESSFUL(m->status_code)) {
+ soup_xmlrpc_extract_method_response(m->response_body->data,
+ m->response_body->length,
+ NULL,
+ G_TYPE_VALUE_ARRAY, user_data);
+ }
+
+ g_main_quit(loop);
+ lock = FALSE;
+}
+
+GValueArray *xmlrpc_get_array(gchar *addr,
+ gchar *method,
+ const gchar *param_types,
+ ...)
+{
+ GValueArray *params, *answer = NULL;
+ SoupMessage *msg;
+ gchar *body;
+
+ msg = soup_message_new("POST", addr);
+
+ params = g_value_array_new(1);
+
+ if (param_types && *param_types) {
+ va_list ap;
+
+ va_start(ap, param_types);
+ while (*param_types) {
+ switch (*param_types) {
+ case '%':
+ break;
+ case 'i':
+ soup_value_array_append(params, G_TYPE_INT, va_arg(ap, int));
+ break;
+ case 's':
+ default:
+ soup_value_array_append(params, G_TYPE_STRING, va_arg(ap, char *));
+ break;
+ }
+
+ param_types++;
+ }
+
+ va_end(ap);
+ }
+
+ body = soup_xmlrpc_build_method_call(method, params->values, params->n_values);
+ g_value_array_free(params);
+
+ soup_message_set_request(msg, "text/xml",
+ SOUP_MEMORY_TAKE, body, strlen(body));
+
+ while (lock)
+ g_main_iteration(FALSE);
+
+ lock = TRUE;
+ soup_session_queue_message(session, msg, xmlrpc_response_get_array, &answer);
+ g_main_run(loop);
+
+ return answer;
+}
+
+#endif /* HAS_LIBSOUP */
+
diff --git a/remote/xmlrpc-server.c b/remote/xmlrpc-server.c
new file mode 100644
index 00000000..e953cfd1
--- /dev/null
+++ b/remote/xmlrpc-server.c
@@ -0,0 +1,782 @@
+/*
+ * HardInfo - Displays System Information
+ * Copyright (C) 2003-2009 Leandro A. F. Pereira <leandro@hardinfo.org>
+ *
+ * This file is based off xmlrpc-server-test.c from libsoup test suite
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#ifdef HAS_LIBSOUP
+#include <stdio.h>
+#include <string.h>
+#include <libsoup/soup.h>
+
+#include "shell.h"
+#include "hardinfo.h"
+
+#define XMLRPC_SERVER_VERSION 1
+/* server namespace */
+static void method_get_api_version(SoupMessage * msg,
+ GValueArray * params);
+static void method_shutdown_server(SoupMessage * msg,
+ GValueArray * params);
+/* module namespace */
+static void method_get_module_list(SoupMessage * msg,
+ GValueArray * params);
+static void method_get_entry_list(SoupMessage * msg, GValueArray * params);
+static void method_entry_reload(SoupMessage * msg, GValueArray * params);
+static void method_entry_scan(SoupMessage * msg, GValueArray * params);
+static void method_entry_get_field(SoupMessage * msg,
+ GValueArray * params);
+static void method_entry_get_moreinfo(SoupMessage * msg,
+ GValueArray * params);
+static void method_entry_get_note(SoupMessage * msg, GValueArray * params);
+static void method_entry_function(SoupMessage * msg, GValueArray * params);
+static void method_get_about_info(SoupMessage * msg, GValueArray * params);
+static void method_call_method(SoupMessage * msg, GValueArray * params);
+static void method_call_method_param(SoupMessage * msg,
+ GValueArray * params);
+
+/* method handler table */
+static const struct {
+ gchar *method_name;
+ void *callback;
+} handler_table[] = {
+ /* server namespace */
+ { "server.getAPIVersion", method_get_api_version },
+ { "server.shutdownServer", method_shutdown_server },
+ /* module namespace */
+ { "module.getModuleList", method_get_module_list },
+ { "module.getEntryList", method_get_entry_list },
+ { "module.entryReload", method_entry_reload },
+ { "module.entryScan", method_entry_scan },
+ { "module.entryFunction", method_entry_function },
+ { "module.entryGetNote", method_entry_get_note },
+ { "module.entryGetField", method_entry_get_field },
+ { "module.entryGetMoreInfo", method_entry_get_moreinfo },
+ { "module.getAboutInfo", method_get_about_info },
+ { "module.callMethod", method_call_method },
+ { "module.callMethodParam", method_call_method_param },
+ { NULL }
+};
+
+static GHashTable *handlers = NULL;
+static GMainLoop *loop = NULL;
+
+typedef struct _MethodParameter MethodParameter;
+struct _MethodParameter {
+ int param_type;
+ void *variable;
+};
+
+static void
+args_error(SoupMessage * msg, GValueArray * params, int expected)
+{
+ soup_xmlrpc_set_fault(msg,
+ SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS,
+ "Wrong number of parameters: expected %d, got %d",
+ expected, params->n_values);
+}
+
+static void
+type_error(SoupMessage * msg, GType expected, GValueArray * params,
+ int bad_value)
+{
+ soup_xmlrpc_set_fault(msg,
+ SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS,
+ "Bad parameter #%d: expected %s, got %s",
+ bad_value + 1, g_type_name(expected),
+ g_type_name(G_VALUE_TYPE
+ (&params->values[bad_value])));
+}
+
+static gboolean validate_parameters(SoupMessage *msg, GValueArray *params,
+ MethodParameter *method_params, gint n_params)
+{
+ int i;
+
+ if (params->n_values != n_params) {
+ args_error(msg, params, n_params);
+ return FALSE;
+ }
+
+ for (i = 0; i < n_params; i++) {
+ if (!soup_value_array_get_nth(params, i,
+ method_params[i].param_type,
+ method_params[i].variable)) {
+ int j;
+
+ type_error(msg, method_params[i].param_type, params, i);
+
+ for (j = 0; j < i; j++) {
+ if (method_params[j].param_type == G_TYPE_STRING) {
+ g_free(method_params[j].variable);
+ }
+ }
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void method_get_module_list(SoupMessage * msg, GValueArray * params)
+{
+ GValueArray *out;
+ GSList *modules;
+
+ out = soup_value_array_new();
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ ShellModule *module = (ShellModule *) modules->data;
+ gchar *icon_file, *tmp;
+ GValueArray *tuple;
+
+ tuple = soup_value_array_new();
+
+ tmp = g_path_get_basename(g_module_name(module->dll));
+ if ((icon_file = g_strrstr(tmp, G_MODULE_SUFFIX))) {
+ *icon_file = '\0';
+ icon_file = g_strconcat(tmp, "png", NULL);
+ } else {
+ icon_file = "";
+ }
+
+ soup_value_array_append(tuple, G_TYPE_STRING, module->name);
+ soup_value_array_append(tuple, G_TYPE_STRING, icon_file);
+
+ soup_value_array_append(out, G_TYPE_VALUE_ARRAY, tuple);
+
+ g_value_array_free(tuple);
+ g_free(tmp);
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_VALUE_ARRAY, out);
+ g_value_array_free(out);
+}
+
+static void method_get_entry_list(SoupMessage * msg, GValueArray * params)
+{
+ ShellModule *module;
+ ShellModuleEntry *module_entry;
+ GSList *entry, *modules;
+ GValueArray *out;
+ gboolean found = FALSE;
+ gchar *module_name;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &module_name }
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ ShellModule *module = (ShellModule *) modules->data;
+
+ if (!strncmp(module->name, module_name, strlen(module->name))) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ out = soup_value_array_new();
+
+ if (found) {
+ module = (ShellModule *) modules->data;
+ for (entry = module->entries; entry; entry = entry->next) {
+ GValueArray *tuple;
+
+ module_entry = (ShellModuleEntry *) entry->data;
+
+ if (module_entry->flags & MODULE_FLAG_NO_REMOTE) {
+ /* do nothing if we're not supposed to */
+ } else {
+ tuple = soup_value_array_new();
+
+ soup_value_array_append(tuple, G_TYPE_STRING, module_entry->name);
+ soup_value_array_append(tuple, G_TYPE_STRING, module_entry->icon_file);
+
+ soup_value_array_append(out, G_TYPE_VALUE_ARRAY, tuple);
+ g_value_array_free(tuple);
+ }
+ }
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_VALUE_ARRAY, out);
+ g_value_array_free(out);
+}
+
+static void method_entry_get_field(SoupMessage * msg, GValueArray * params)
+{
+ ShellModule *module;
+ GSList *modules;
+ gchar *module_name, *field_name, *answer = NULL;
+ gint entry_number;
+ gboolean found = FALSE;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &module_name },
+ { G_TYPE_INT, &entry_number },
+ { G_TYPE_STRING, &field_name }
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ module = (ShellModule *) modules->data;
+
+ if (!strncmp(module->name, module_name, strlen(module->name))) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found) {
+ if (entry_number < g_slist_length(module->entries)) {
+ GSList *entry_node = g_slist_nth(module->entries, entry_number);
+ ShellModuleEntry *entry = (ShellModuleEntry *)entry_node->data;
+
+ if (entry->flags & MODULE_FLAG_NO_REMOTE) {
+ /* do nothing */
+ } else {
+ answer = module_entry_get_field(entry, field_name);
+ }
+ }
+ }
+
+ if (!answer) {
+ answer = g_strdup("");
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_STRING, answer);
+ g_free(answer);
+}
+
+static void method_entry_get_moreinfo(SoupMessage * msg,
+ GValueArray * params)
+{
+ ShellModule *module;
+ GSList *modules;
+ gchar *module_name, *field_name, *answer = NULL;
+ gint entry_number;
+ gboolean found = FALSE;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &module_name },
+ { G_TYPE_INT, &entry_number },
+ { G_TYPE_STRING, &field_name },
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ module = (ShellModule *) modules->data;
+
+ if (!strncmp(module->name, module_name, strlen(module->name))) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found) {
+ if (entry_number < g_slist_length(module->entries)) {
+ GSList *entry_node = g_slist_nth(module->entries, entry_number);
+ ShellModuleEntry *entry = (ShellModuleEntry *)entry_node->data;
+
+ if (entry->flags & MODULE_FLAG_NO_REMOTE) {
+ /* do nothing */
+ } else {
+ answer = module_entry_get_moreinfo(entry, field_name);
+ }
+ }
+ }
+
+ if (!answer) {
+ answer = g_strdup("");
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_STRING, answer);
+ g_free(answer);
+}
+
+static void method_entry_reload(SoupMessage * msg, GValueArray * params)
+{
+ ShellModule *module;
+ GSList *modules;
+ gchar *module_name;
+ gint entry_number;
+ gboolean found = FALSE, answer = FALSE;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &module_name },
+ { G_TYPE_INT, &entry_number },
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ module = (ShellModule *) modules->data;
+
+ if (!strncmp(module->name, module_name, strlen(module->name))) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found) {
+ if (entry_number < g_slist_length(module->entries)) {
+ GSList *entry_node = g_slist_nth(module->entries, entry_number);
+ ShellModuleEntry *entry = (ShellModuleEntry *)entry_node->data;
+
+ if (entry->flags & MODULE_FLAG_NO_REMOTE) {
+ /* do nothing */
+ } else {
+ module_entry_reload(entry);
+ answer = TRUE;
+ }
+ }
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_BOOLEAN, answer);
+}
+
+static void method_entry_scan(SoupMessage * msg, GValueArray * params)
+{
+ ShellModule *module;
+ GSList *modules;
+ gchar *module_name;
+ gint entry_number;
+ gboolean found = FALSE, answer = FALSE;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &module_name },
+ { G_TYPE_INT, &entry_number },
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ module = (ShellModule *) modules->data;
+
+ if (!strncmp(module->name, module_name, strlen(module->name))) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found) {
+ if (entry_number < g_slist_length(module->entries)) {
+ GSList *entry_node = g_slist_nth(module->entries, entry_number);
+ ShellModuleEntry *entry = (ShellModuleEntry *)entry_node->data;
+
+ if (entry->flags & MODULE_FLAG_NO_REMOTE) {
+ /* do nothing */
+ } else {
+ module_entry_scan(entry);
+ answer = TRUE;
+ }
+ }
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_BOOLEAN, answer);
+}
+
+static void method_entry_function(SoupMessage * msg, GValueArray * params)
+{
+ ShellModule *module;
+ GSList *modules;
+ gchar *module_name, *answer = NULL;
+ gboolean found = FALSE;
+ gint entry_number;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &module_name },
+ { G_TYPE_INT, &entry_number },
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ module = (ShellModule *) modules->data;
+
+ if (!strncmp(module->name, module_name, strlen(module->name))) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found) {
+ if (entry_number < g_slist_length(module->entries)) {
+ GSList *entry_node = g_slist_nth(module->entries, entry_number);
+ ShellModuleEntry *entry = (ShellModuleEntry *)entry_node->data;
+
+ if (entry->flags & MODULE_FLAG_NO_REMOTE) {
+ /* do nothing */
+ } else {
+ module_entry_scan(entry);
+ answer = module_entry_function(entry);
+ }
+ }
+ }
+
+ if (!answer) {
+ answer = g_strdup("");
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_STRING, answer);
+ g_free(answer);
+}
+
+
+static void method_entry_get_note(SoupMessage * msg, GValueArray * params)
+{
+ ShellModule *module;
+ GSList *modules;
+ gchar *module_name, *answer = NULL;
+ gint entry_number;
+ gboolean found = FALSE;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &module_name },
+ { G_TYPE_INT, &entry_number },
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ module = (ShellModule *) modules->data;
+
+ if (!strncmp(module->name, module_name, strlen(module->name))) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found) {
+ if (entry_number < g_slist_length(module->entries)) {
+ GSList *entry_node = g_slist_nth(module->entries, entry_number);
+ ShellModuleEntry *entry = (ShellModuleEntry *)entry_node->data;
+
+ if (entry->flags & MODULE_FLAG_NO_REMOTE) {
+ /* do nothing */
+ } else {
+ answer = g_strdup((gchar *)module_entry_get_note(entry));
+ }
+ }
+ }
+
+ if (!answer) {
+ answer = g_strdup("");
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_STRING, answer);
+ g_free(answer);
+}
+
+static void method_get_about_info(SoupMessage * msg, GValueArray * params)
+{
+ ShellModule *module;
+ GSList *modules;
+ gchar *module_name;
+ gboolean found = FALSE;
+ GValueArray *out;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &module_name },
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ for (modules = modules_get_list(); modules; modules = modules->next) {
+ module = (ShellModule *) modules->data;
+
+ if (!strncmp(module->name, module_name, strlen(module->name))) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ out = soup_value_array_new();
+
+ if (found) {
+ ModuleAbout *about = module_get_about(module);
+
+ soup_value_array_append(out, G_TYPE_STRING, about->description);
+ soup_value_array_append(out, G_TYPE_STRING, about->author);
+ soup_value_array_append(out, G_TYPE_STRING, about->version);
+ soup_value_array_append(out, G_TYPE_STRING, about->license);
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_VALUE_ARRAY, out);
+ g_value_array_free(out);
+}
+
+static void method_call_method(SoupMessage * msg, GValueArray * params)
+{
+ gchar *method_name, *answer = NULL;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &method_name },
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ if (!(answer = module_call_method(method_name))) {
+ answer = g_strdup("");
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_STRING, answer);
+ g_free(answer);
+}
+
+static void method_call_method_param(SoupMessage * msg,
+ GValueArray * params)
+{
+ gchar *method_name, *parameter, *answer = NULL;
+ MethodParameter method_params[] = {
+ { G_TYPE_STRING, &method_name },
+ { G_TYPE_STRING, &parameter },
+ };
+
+ if (!validate_parameters(msg, params, method_params, G_N_ELEMENTS(method_params))) {
+ return;
+ }
+
+ if (!(answer = module_call_method_param(method_name, parameter))) {
+ answer = g_strdup("");
+ }
+
+ soup_xmlrpc_set_response(msg, G_TYPE_STRING, answer);
+ g_free(answer);
+}
+
+static void method_get_api_version(SoupMessage * msg, GValueArray * params)
+{
+ soup_xmlrpc_set_response(msg, G_TYPE_INT, XMLRPC_SERVER_VERSION);
+}
+
+static void method_shutdown_server(SoupMessage * msg, GValueArray * params)
+{
+ soup_xmlrpc_set_response(msg, G_TYPE_BOOLEAN, TRUE);
+
+ g_main_loop_quit(loop);
+}
+#endif /* HAS_LIBSOUP */
+
+void xmlrpc_server_init(void)
+{
+#ifdef HAS_LIBSOUP
+ if (!loop) {
+ DEBUG("creating main loop");
+ loop = g_main_loop_new(NULL, FALSE);
+ } else {
+ DEBUG("using main loop instance %p", loop);
+ }
+
+ if (!handlers) {
+ int i;
+ handlers = g_hash_table_new(g_str_hash, g_str_equal);
+
+ DEBUG("registering handlers");
+
+ for (i = 0; handler_table[i].method_name; i++) {
+ g_hash_table_insert(handlers,
+ handler_table[i].method_name,
+ handler_table[i].callback);
+ }
+ }
+#endif /* HAS_LIBSOUP */
+}
+
+#ifdef HAS_LIBSOUP
+static SoupServer *xmlrpc_server_new(void)
+{
+ SoupServer *server;
+
+ DEBUG("creating server");
+ server = soup_server_new(SOUP_SERVER_SSL_CERT_FILE, NULL,
+ SOUP_SERVER_SSL_KEY_FILE, NULL,
+ SOUP_SERVER_ASYNC_CONTEXT, NULL,
+ SOUP_SERVER_PORT, 4242, NULL);
+ if (!server) {
+ return NULL;
+ }
+
+ soup_server_run_async(server);
+
+ return server;
+}
+
+static void xmlrpc_server_callback(SoupServer * server,
+ SoupMessage * msg,
+ const char *path,
+ GHashTable * query,
+ SoupClientContext * context,
+ gpointer data)
+{
+ if (msg->method == SOUP_METHOD_POST) {
+ gchar *method_name;
+ GValueArray *params;
+ void (*callback) (SoupMessage * msg, GValueArray * params);
+
+ DEBUG("POST %s", path);
+
+ if (!soup_xmlrpc_parse_method_call(msg->request_body->data,
+ msg->request_body->length,
+ &method_name, &params)) {
+ soup_message_set_status(msg, SOUP_STATUS_BAD_REQUEST);
+ return;
+ }
+
+ DEBUG("method: %s", method_name);
+
+ if ((callback = g_hash_table_lookup(handlers, method_name))) {
+ soup_message_set_status(msg, SOUP_STATUS_OK);
+
+ DEBUG("found callback: %p", callback);
+ callback(msg, params);
+ } else {
+ DEBUG("callback not found");
+ soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
+ }
+
+ g_value_array_free(params);
+ g_free(method_name);
+ } else {
+ DEBUG("received request of unknown method");
+ soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
+ }
+}
+
+static void icon_server_callback(SoupServer * server,
+ SoupMessage * msg,
+ const char *path,
+ GHashTable * query,
+ SoupClientContext * context,
+ gpointer data)
+{
+ if (msg->method == SOUP_METHOD_GET) {
+ path = g_strrstr(path, "/");
+
+ DEBUG("GET %s", path);
+
+ if (!path || !g_str_has_suffix(path, ".png")) {
+ DEBUG("not an icon, invalid path, etc");
+ soup_message_set_status(msg, SOUP_STATUS_FORBIDDEN);
+ soup_message_set_response(msg,
+ "text/plain",
+ SOUP_MEMORY_STATIC,
+ "500 :(", 6);
+ } else {
+ gchar *file, *icon;
+ gsize size;
+
+ file = g_build_filename(params.path_data,
+ "pixmaps",
+ path + 1,
+ NULL);
+
+ if (g_file_get_contents(file, &icon, &size, NULL)) {
+ DEBUG("icon found");
+ soup_message_set_status(msg, SOUP_STATUS_OK);
+ soup_message_set_response(msg,
+ "image/png",
+ SOUP_MEMORY_TAKE,
+ icon, size);
+ } else {
+ DEBUG("icon not found");
+ soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND);
+ soup_message_set_response(msg,
+ "text/plain",
+ SOUP_MEMORY_STATIC,
+ "404 :(", 6);
+ }
+
+ g_free(file);
+ }
+ } else {
+ DEBUG("received request of unknown method");
+ soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
+ }
+}
+#endif /* HAS_LIBSOUP */
+
+void xmlrpc_server_start(GMainLoop *main_loop)
+{
+#ifdef HAS_LIBSOUP
+ SoupServer *server;
+
+ if (main_loop) {
+ loop = main_loop;
+ }
+
+ if (!loop || !handlers) {
+ DEBUG("initializing server");
+ xmlrpc_server_init();
+ }
+
+ server = xmlrpc_server_new();
+ if (!server) {
+ if (main_loop) {
+ g_warning("Cannot create XML-RPC server.");
+ return;
+ } else {
+ g_error("Cannot create XML-RPC server. Aborting");
+ }
+ }
+
+ DEBUG("adding soup handlers for /xmlrpc");
+ soup_server_add_handler(server, "/xmlrpc", xmlrpc_server_callback,
+ NULL, NULL);
+ DEBUG("adding soup handlers for /icon/");
+ soup_server_add_handler(server, "/icon/", icon_server_callback,
+ NULL, NULL);
+
+ DEBUG("starting server");
+ g_print("XML-RPC server ready\n");
+ g_main_loop_run(loop);
+
+ DEBUG("shutting down server");
+ g_main_loop_unref(loop);
+ soup_server_quit(server);
+ g_object_unref(server);
+#endif /* HAS_LIBSOUP */
+}
+
+#ifdef XMLRPC_SERVER_TEST
+int main(void)
+{
+#ifdef HAS_LIBSOUP
+ g_type_init();
+
+ xmlrpc_server_init();
+ xmlrpc_server_start();
+#endif /* HAS_LIBSOUP */
+}
+#endif /* XMLRPC_SERVER_TEST */