/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager Connection editor -- Connection editor for NetworkManager * * Dan Williams * * 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; either version 2 of the License, or * (at your option) any later version. * * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. * * (C) Copyright 2008 Red Hat, Inc. */ #include "config.h" #include #include #include #include #include #include #include #include #include "vpn-helpers.h" #define NM_VPN_API_SUBJECT_TO_CHANGE #include "nm-vpn-plugin-ui-interface.h" static GHashTable *plugins = NULL; NMVpnPluginUiInterface * vpn_get_plugin_by_service (const char *service) { g_return_val_if_fail (service != NULL, NULL); if (!plugins) { vpn_get_plugins (NULL); if (!plugins) return NULL; } return g_hash_table_lookup (plugins, service); } GHashTable * vpn_get_plugins (GError **error) { GDir *dir; const char *f; if (error) g_return_val_if_fail (*error == NULL, NULL); if (plugins) return plugins; dir = g_dir_open (NM_VPN_CONFIG_DIR, 0, error); if (!dir) return NULL; plugins = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref); while ((f = g_dir_read_name (dir))) { char *path = NULL, *service = NULL; char *so_path = NULL, *so_name = NULL; GKeyFile *keyfile = NULL; GModule *module; NMVpnPluginUiFactory factory = NULL; if (!g_str_has_suffix (f, ".name")) continue; path = g_strdup_printf ("%s/%s", NM_VPN_CONFIG_DIR, f); keyfile = g_key_file_new (); if (!g_key_file_load_from_file (keyfile, path, 0, NULL)) goto next; service = g_key_file_get_string (keyfile, "VPN Connection", "service", NULL); if (!service) goto next; so_path = g_key_file_get_string (keyfile, "GNOME", "properties", NULL); if (!so_path) goto next; /* Remove any path and extension components, then reconstruct path * to the SO in LIBDIR */ so_name = g_path_get_basename (so_path); g_free (so_path); so_path = g_build_filename (NM_VPN_MODULE_DIR, so_name, NULL); g_free (so_name); module = g_module_open (so_path, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); if (!module) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Cannot load the VPN plugin which provides the " "service '%s'.", service); goto next; } if (g_module_symbol (module, "nm_vpn_plugin_ui_factory", (gpointer) &factory)) { NMVpnPluginUiInterface *plugin; GError *factory_error = NULL; gboolean success = FALSE; plugin = factory (&factory_error); if (plugin) { char *plug_name = NULL, *plug_service = NULL; /* Validate plugin properties */ g_object_get (G_OBJECT (plugin), NM_VPN_PLUGIN_UI_INTERFACE_NAME, &plug_name, NM_VPN_PLUGIN_UI_INTERFACE_SERVICE, &plug_service, NULL); if (!plug_name || !strlen (plug_name)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot load VPN plugin in '%s': missing plugin name", g_module_name (module)); } else if (!plug_service || strcmp (plug_service, service)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot load VPN plugin in '%s': invalid service name", g_module_name (module)); } else { /* Success! */ g_object_set_data_full (G_OBJECT (plugin), "gmodule", module, (GDestroyNotify) g_module_close); g_hash_table_insert (plugins, g_strdup (service), plugin); success = TRUE; } g_free (plug_name); g_free (plug_service); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot load VPN plugin in '%s': %s", g_module_name (module), g_module_error ()); } if (!success) g_module_close (module); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot locate nm_vpn_plugin_ui_factory() in '%s': %s", g_module_name (module), g_module_error ()); g_module_close (module); } next: g_free (so_path); g_free (service); g_key_file_free (keyfile); g_free (path); } g_dir_close (dir); return plugins; } typedef struct { VpnImportCallback callback; gpointer user_data; } ActionInfo; static void import_vpn_from_file_cb (GtkWidget *dialog, gint response, gpointer user_data) { char *filename = NULL; ActionInfo *info = (ActionInfo *) user_data; GHashTableIter iter; gpointer key; NMVpnPluginUiInterface *plugin; NMConnection *connection = NULL; GError *error = NULL; if (response != GTK_RESPONSE_ACCEPT) goto out; filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); if (!filename) { g_warning ("%s: didn't get a filename back from the chooser!", __func__); goto out; } g_hash_table_iter_init (&iter, plugins); while (!connection && g_hash_table_iter_next (&iter, &key, (gpointer *)&plugin)) { g_clear_error (&error); connection = nm_vpn_plugin_ui_interface_import (plugin, filename, &error); } if (!connection) { GtkWidget *err_dialog; char *bname = g_path_get_basename (filename); err_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Cannot import VPN connection")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (err_dialog), _("The file '%s' could not be read or does not contain recognized VPN connection information\n\nError: %s."), bname, error ? error->message : "unknown error"); g_free (bname); g_signal_connect (err_dialog, "delete-event", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect (err_dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); gtk_dialog_run (GTK_DIALOG (err_dialog)); } g_clear_error (&error); g_free (filename); out: gtk_widget_hide (dialog); gtk_widget_destroy (dialog); info->callback (connection, info->user_data); g_free (info); } static void destroy_import_chooser (GtkWidget *dialog, gpointer user_data) { ActionInfo *info = (ActionInfo *) user_data; gtk_widget_destroy (dialog); info->callback (NULL, info->user_data); g_free (info); } void vpn_import (GtkWindow *parent, VpnImportCallback callback, gpointer user_data) { GtkWidget *dialog; ActionInfo *info; const char *home_folder; dialog = gtk_file_chooser_dialog_new (_("Select file to import"), parent, GTK_FILE_CHOOSER_ACTION_OPEN, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_ACCEPT, NULL); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); home_folder = g_get_home_dir (); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), home_folder); info = g_malloc0 (sizeof (ActionInfo)); info->callback = callback; info->user_data = user_data; g_signal_connect (G_OBJECT (dialog), "close", G_CALLBACK (destroy_import_chooser), info); g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (import_vpn_from_file_cb), info); gtk_widget_show_all (dialog); gtk_window_present (GTK_WINDOW (dialog)); } static void export_vpn_to_file_cb (GtkWidget *dialog, gint response, gpointer user_data) { NMConnection *connection = NM_CONNECTION (user_data); char *filename = NULL; GError *error = NULL; NMVpnPluginUiInterface *plugin; NMSettingConnection *s_con = NULL; NMSettingVPN *s_vpn = NULL; const char *service_type; const char *id = NULL; gboolean success = FALSE; if (response != GTK_RESPONSE_ACCEPT) goto out; filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); if (!filename) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "no filename"); goto done; } if (g_file_test (filename, G_FILE_TEST_EXISTS)) { int replace_response; GtkWidget *replace_dialog; char *bname; bname = g_path_get_basename (filename); replace_dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_CANCEL, _("A file named \"%s\" already exists."), bname); gtk_dialog_add_buttons (GTK_DIALOG (replace_dialog), _("_Replace"), GTK_RESPONSE_OK, NULL); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (replace_dialog), _("Do you want to replace %s with the VPN connection you are saving?"), bname); g_free (bname); replace_response = gtk_dialog_run (GTK_DIALOG (replace_dialog)); gtk_widget_destroy (replace_dialog); if (replace_response != GTK_RESPONSE_OK) goto out; } s_con = nm_connection_get_setting_connection (connection); id = s_con ? nm_setting_connection_get_id (s_con) : NULL; if (!id) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "connection setting invalid"); goto done; } s_vpn = nm_connection_get_setting_vpn (connection); service_type = s_vpn ? nm_setting_vpn_get_service_type (s_vpn) : NULL; if (!service_type) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "VPN setting invalid"); goto done; } plugin = vpn_get_plugin_by_service (service_type); if (plugin) success = nm_vpn_plugin_ui_interface_export (plugin, filename, connection, &error); done: if (!success) { GtkWidget *err_dialog; char *bname = filename ? g_path_get_basename (filename) : g_strdup ("(none)"); err_dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Cannot export VPN connection")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (err_dialog), _("The VPN connection '%s' could not be exported to %s.\n\nError: %s."), id ? id : "(unknown)", bname, error ? error->message : "unknown error"); g_free (bname); g_signal_connect (err_dialog, "delete-event", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect (err_dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); gtk_widget_show_all (err_dialog); gtk_window_present (GTK_WINDOW (err_dialog)); } out: if (error) g_error_free (error); g_object_unref (connection); gtk_widget_hide (dialog); gtk_widget_destroy (dialog); } void vpn_export (NMConnection *connection) { GtkWidget *dialog; NMVpnPluginUiInterface *plugin; NMSettingVPN *s_vpn = NULL; const char *service_type; const char *home_folder; s_vpn = nm_connection_get_setting_vpn (connection); service_type = s_vpn ? nm_setting_vpn_get_service_type (s_vpn) : NULL; if (!service_type) { g_warning ("%s: invalid VPN connection!", __func__); return; } dialog = gtk_file_chooser_dialog_new (_("Export VPN connection..."), NULL, GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_ACCEPT, NULL); home_folder = g_get_home_dir (); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), home_folder); plugin = vpn_get_plugin_by_service (service_type); if (plugin) { char *suggested = NULL; suggested = nm_vpn_plugin_ui_interface_get_suggested_name (plugin, connection); if (suggested) { gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), suggested); g_free (suggested); } } g_signal_connect (G_OBJECT (dialog), "close", G_CALLBACK (gtk_widget_destroy), NULL); g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (export_vpn_to_file_cb), g_object_ref (connection)); gtk_widget_show_all (dialog); gtk_window_present (GTK_WINDOW (dialog)); } gboolean vpn_supports_ipv6 (NMConnection *connection) { NMSettingVPN *s_vpn; const char *service_type; NMVpnPluginUiInterface *plugin; guint32 capabilities; s_vpn = nm_connection_get_setting_vpn (connection); g_return_val_if_fail (s_vpn != NULL, FALSE); service_type = nm_setting_vpn_get_service_type (s_vpn); g_return_val_if_fail (service_type != NULL, FALSE); plugin = vpn_get_plugin_by_service (service_type); g_return_val_if_fail (plugin != NULL, FALSE); capabilities = nm_vpn_plugin_ui_interface_get_capabilities (plugin); return (capabilities & NM_VPN_PLUGIN_UI_CAPABILITY_IPV6) != 0; }