diff options
author | Leandro A. F. Pereira <leandro@hardinfo.org> | 2010-05-03 09:25:43 -0300 |
---|---|---|
committer | Leandro A. F. Pereira <leandro@hardinfo.org> | 2010-05-03 09:25:43 -0300 |
commit | 9a50155ec3e27aa6cedf3f118196f1947c769a29 (patch) | |
tree | c23cc6949b227b9e09432af038a44ccb81d92ce6 /hardinfo2/remote | |
parent | ca5a3e84296a34c2391374942001cfaf1d8395a6 (diff) |
Use CMake instead of ToscoConf.
Diffstat (limited to 'hardinfo2/remote')
-rw-r--r-- | hardinfo2/remote/remote.c | 1307 | ||||
-rw-r--r-- | hardinfo2/remote/ssh-conn.c | 338 | ||||
-rw-r--r-- | hardinfo2/remote/xmlrpc-client.c | 247 | ||||
-rw-r--r-- | hardinfo2/remote/xmlrpc-server.c | 782 |
4 files changed, 2674 insertions, 0 deletions
diff --git a/hardinfo2/remote/remote.c b/hardinfo2/remote/remote.c new file mode 100644 index 00000000..d266735d --- /dev/null +++ b/hardinfo2/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/hardinfo2/remote/ssh-conn.c b/hardinfo2/remote/ssh-conn.c new file mode 100644 index 00000000..7f099d35 --- /dev/null +++ b/hardinfo2/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/hardinfo2/remote/xmlrpc-client.c b/hardinfo2/remote/xmlrpc-client.c new file mode 100644 index 00000000..d363729b --- /dev/null +++ b/hardinfo2/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/hardinfo2/remote/xmlrpc-server.c b/hardinfo2/remote/xmlrpc-server.c new file mode 100644 index 00000000..e953cfd1 --- /dev/null +++ b/hardinfo2/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 + (¶ms->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, ¶meter }, + }; + + 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, ¶ms)) { + 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 */ |