// SPDX-License-Identifier: GPL-2.0+ /* NetworkManager Connection editor -- Connection editor for NetworkManager * * Dan Williams * * Copyright 2008 - 2014 Red Hat, Inc. */ #include "nm-default.h" #include #include #include #include #include "ce-page.h" G_DEFINE_ABSTRACT_TYPE (CEPage, ce_page, G_TYPE_OBJECT) enum { PROP_0, PROP_CONNECTION, PROP_PARENT_WINDOW, LAST_PROP }; enum { CHANGED, INITIALIZED, NEW_EDITOR, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; typedef struct { int value; const char *text; } SpinMapping; static gboolean spin_output_with_mapping (GtkSpinButton *spin, const SpinMapping *mapping) { int val; gchar *buf = NULL; val = gtk_spin_button_get_value_as_int (spin); if (val == mapping->value) buf = g_strdup (mapping->text); else buf = g_strdup_printf ("%d", val); if (!nm_streq (buf, gtk_entry_get_text (GTK_ENTRY (spin)))) gtk_entry_set_text (GTK_ENTRY (spin), buf); g_free (buf); return TRUE; } static gint spin_input_with_mapping (GtkSpinButton *spin, gdouble *new_val, const SpinMapping *mapping) { const gchar *buf; buf = gtk_entry_get_text (GTK_ENTRY (spin)); if (nm_streq (buf, mapping->text)) { *new_val = mapping->value; return TRUE; } return FALSE; } static void spin_set_mapping (GtkSpinButton *spin, int value, const char *text) { SpinMapping *mapping; g_return_if_fail (!g_object_get_data (G_OBJECT (spin), "mapping")); mapping = g_new (SpinMapping, 1); *mapping = (SpinMapping) { .value = value, .text = text, }; g_object_set_data_full (G_OBJECT (spin), "mapping", mapping, g_free); g_signal_connect (spin, "output", G_CALLBACK (spin_output_with_mapping), mapping); g_signal_connect (spin, "input", G_CALLBACK (spin_input_with_mapping), mapping); } void ce_spin_automatic_val (GtkSpinButton *spin, int defvalue) { spin_set_mapping (spin, defvalue, _("automatic")); } void ce_spin_default_val (GtkSpinButton *spin, int defvalue) { spin_set_mapping (spin, defvalue, _("default")); } void ce_spin_off_val (GtkSpinButton *spin, int defvalue) { spin_set_mapping (spin, defvalue, _("off")); } int ce_get_property_default (NMSetting *setting, const char *property_name) { GParamSpec *spec; GValue value = { 0, }; g_return_val_if_fail (NM_IS_SETTING (setting), -1); spec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), property_name); g_return_val_if_fail (spec != NULL, -1); g_value_init (&value, spec->value_type); g_param_value_set_default (spec, &value); if (G_VALUE_HOLDS_CHAR (&value)) return (int) g_value_get_schar (&value); else if (G_VALUE_HOLDS_INT (&value)) return g_value_get_int (&value); else if (G_VALUE_HOLDS_INT64 (&value)) return (int) g_value_get_int64 (&value); else if (G_VALUE_HOLDS_LONG (&value)) return (int) g_value_get_long (&value); else if (G_VALUE_HOLDS_UINT (&value)) return (int) g_value_get_uint (&value); else if (G_VALUE_HOLDS_UINT64 (&value)) return (int) g_value_get_uint64 (&value); else if (G_VALUE_HOLDS_ULONG (&value)) return (int) g_value_get_ulong (&value); else if (G_VALUE_HOLDS_UCHAR (&value)) return (int) g_value_get_uchar (&value); g_return_val_if_fail (FALSE, 0); return 0; } gboolean ce_page_validate (CEPage *self, NMConnection *connection, GError **error) { g_return_val_if_fail (CE_IS_PAGE (self), FALSE); g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); if (CE_PAGE_GET_CLASS (self)->ce_page_validate_v) { if (!CE_PAGE_GET_CLASS (self)->ce_page_validate_v (self, connection, error)) { if (error && !*error) g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("unspecified error")); return FALSE; } } return TRUE; } gboolean ce_page_last_update (CEPage *self, NMConnection *connection, GError **error) { g_return_val_if_fail (CE_IS_PAGE (self), FALSE); g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); if (CE_PAGE_GET_CLASS (self)->last_update) return CE_PAGE_GET_CLASS (self)->last_update (self, connection, error); return TRUE; } gboolean ce_page_inter_page_change (CEPage *self) { gboolean ret = FALSE; g_return_val_if_fail (CE_IS_PAGE (self), FALSE); if (self->inter_page_change_running) return FALSE; self->inter_page_change_running = TRUE; if (CE_PAGE_GET_CLASS (self)->inter_page_change) ret = CE_PAGE_GET_CLASS (self)->inter_page_change (self); self->inter_page_change_running = FALSE; return ret; } static void _set_active_combo_item (GtkComboBox *combo, const char *item, const char *combo_item, int combo_idx) { GtkWidget *entry; if (item) { /* set active item */ gtk_combo_box_set_active (combo, combo_idx); if (!combo_item) gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo), item); entry = gtk_bin_get_child (GTK_BIN (combo)); if (entry) gtk_entry_set_text (GTK_ENTRY (entry), combo_item ? combo_item : item); } } /* Combo box storing data in the form of "text1 (text2)" */ void ce_page_setup_data_combo (CEPage *self, GtkComboBox *combo, const char *data, char **list) { char **iter, *active_item = NULL; int i, active_idx = -1; int data_len; if (data) data_len = strlen (data); else data_len = -1; for (iter = list, i = 0; iter && *iter; iter++, i++) { gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), *iter); if ( data && g_ascii_strncasecmp (*iter, data, data_len) == 0 && ((*iter)[data_len] == '\0' || (*iter)[data_len] == ' ')) { active_item = *iter; active_idx = i; } } _set_active_combo_item (combo, data, active_item, active_idx); } /* Combo box storing MAC addresses only */ void ce_page_setup_mac_combo (CEPage *self, GtkComboBox *combo, const char *mac, char **mac_list) { char **iter, *active_mac = NULL; int i, active_idx = -1; for (iter = mac_list, i = 0; iter && *iter; iter++, i++) { gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), *iter); if (mac && *iter && nm_utils_hwaddr_matches (mac, -1, *iter, -1)) { active_mac = *iter; active_idx = i; } } _set_active_combo_item (combo, mac, active_mac, active_idx); } void ce_page_setup_cloned_mac_combo (GtkComboBoxText *combo, const char *current) { GtkWidget *entry; static const char *entries[][2] = { { "preserve", N_("Preserve") }, { "permanent", N_("Permanent") }, { "random", N_("Random") }, { "stable", N_("Stable") } }; int i, active = -1; gtk_widget_set_tooltip_text (GTK_WIDGET (combo), _("The MAC address entered here will be used as hardware address for " "the network device this connection is activated on. This feature is " "known as MAC cloning or spoofing. Example: 00:11:22:33:44:55")); gtk_combo_box_text_remove_all (combo); for (i = 0; i < G_N_ELEMENTS (entries); i++) { gtk_combo_box_text_append (combo, entries[i][0], _(entries[i][1])); if (nm_streq0 (current, entries[i][0])) active = i; } if (active != -1) { gtk_combo_box_set_active (GTK_COMBO_BOX (combo), active); } else if (current && current[0]) { entry = gtk_bin_get_child (GTK_BIN (combo)); g_assert (entry); gtk_entry_set_text (GTK_ENTRY (entry), current); } } char * ce_page_cloned_mac_get (GtkComboBoxText *combo) { const char *id; id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo)); if (id) return g_strdup (id); return gtk_combo_box_text_get_active_text (combo); } static gboolean mac_valid (const char *mac, int type, const char *property_name, GError **error) { if (mac && *mac) { if (!nm_utils_hwaddr_valid (mac, nm_utils_hwaddr_len (type))) { const char *addr_type; addr_type = type == ARPHRD_ETHER ? _("MAC address") : _("HW address"); if (property_name) { g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid %s for %s (%s)"), addr_type, property_name, mac); } else { g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid %s (%s)"), addr_type, mac); } return FALSE; } } return TRUE; } gboolean ce_page_cloned_mac_combo_valid (GtkComboBoxText *combo, int type, const char *property_name, GError **error) { gs_free char *text = NULL; if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) != -1) return TRUE; text = gtk_combo_box_text_get_active_text (combo); return mac_valid (text, type, property_name, error); } gboolean ce_page_mac_entry_valid (GtkEntry *entry, int type, const char *property_name, GError **error) { g_return_val_if_fail (GTK_IS_ENTRY (entry), FALSE); return mac_valid (gtk_entry_get_text (entry), type, property_name, error); } gboolean ce_page_interface_name_valid (const char *iface, const char *property_name, GError **error) { if (iface && *iface) { if (!nm_utils_is_valid_iface_name (iface, error)) { if (property_name) { g_prefix_error (error, _("invalid interface-name for %s (%s): "), property_name, iface); } else { g_prefix_error (error, _("invalid interface-name (%s): "), iface); } return FALSE; } } return TRUE; } static char ** _get_device_list (CEPage *self, GType device_type, gboolean set_ifname, const char *mac_property) { const GPtrArray *devices; GPtrArray *interfaces; int i; g_return_val_if_fail (CE_IS_PAGE (self), NULL); g_return_val_if_fail (set_ifname || mac_property, NULL); if (!self->client) return NULL; interfaces = g_ptr_array_new (); devices = nm_client_get_devices (self->client); for (i = 0; i < devices->len; i++) { NMDevice *dev = g_ptr_array_index (devices, i); const char *ifname; char *mac = NULL; char *item; if ( device_type != G_TYPE_NONE && !G_TYPE_CHECK_INSTANCE_TYPE (dev, device_type)) continue; if (device_type == NM_TYPE_DEVICE_BT) ifname = nm_device_bt_get_name (NM_DEVICE_BT (dev)); else ifname = nm_device_get_iface (NM_DEVICE (dev)); if (mac_property) g_object_get (G_OBJECT (dev), mac_property, &mac, NULL); if (mac && !mac[0]) nm_clear_g_free (&mac); if (set_ifname && mac_property) item = g_strdup_printf ("%s%s%s%s", ifname, NM_PRINT_FMT_QUOTED (mac, " (", mac, ")", "")); else item = g_strdup (set_ifname ? ifname : mac); if (item) g_ptr_array_add (interfaces, item); g_free (mac); } g_ptr_array_add (interfaces, NULL); return (char **)g_ptr_array_free (interfaces, FALSE); } static gboolean _device_entry_parse (const char *entry_text, char **first, char **second) { const char *sp, *left, *right; if (!entry_text || !*entry_text) { *first = NULL; *second = NULL; return TRUE; } sp = strstr (entry_text, " ("); if (sp) { *first = g_strndup (entry_text, sp - entry_text); left = sp + 1; right = strchr (left, ')'); if (*left == '(' && right && right > left) *second = g_strndup (left + 1, right - left - 1); else { *second = NULL; return FALSE; } } else { *first = g_strdup (entry_text); *second = NULL; } return TRUE; } static gboolean _device_entries_match (const char *ifname, const char *mac, const char *entry) { char *first, *second; gboolean ifname_match = FALSE, mac_match = FALSE; gboolean both; if (!ifname && !mac) return FALSE; _device_entry_parse (entry, &first, &second); both = first && second; if ( ifname && ( !g_strcmp0 (ifname, first) || !g_strcmp0 (ifname, second))) ifname_match = TRUE; if ( mac && ( (first && nm_utils_hwaddr_matches (mac, -1, first, -1)) || (second && nm_utils_hwaddr_matches (mac, -1, second, -1)))) mac_match = TRUE; g_free (first); g_free (second); if (both) return ifname_match && mac_match; else { if (ifname) return ifname_match; else return mac_match; } } /* Combo box storing ifname and/or MAC */ void ce_page_setup_device_combo (CEPage *self, GtkComboBox *combo, GType device_type, const char *ifname, const char *mac, const char *mac_property) { char **iter, *active_item = NULL; int i, active_idx = -1; char **device_list; char *item; device_list = _get_device_list (self, device_type, TRUE, mac_property); if (ifname && mac) item = g_strdup_printf ("%s (%s)", ifname, mac); else if (!ifname && !mac) item = NULL; else item = g_strdup (ifname ? ifname : mac); for (iter = device_list, i = 0; iter && *iter; iter++, i++) { gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), *iter); if (_device_entries_match (ifname, mac, *iter)) { active_item = *iter; active_idx = i; } } _set_active_combo_item (combo, item, active_item, active_idx); g_free (item); g_strfreev (device_list); } gboolean ce_page_device_entry_get (GtkEntry *entry, int type, gboolean check_ifname, char **ifname, char **mac, const char *device_name, GError **error) { gs_free char *first = NULL; gs_free char *second = NULL; const char *ifname_tmp = NULL, *mac_tmp = NULL; const char *str; g_return_val_if_fail (entry != NULL, FALSE); g_return_val_if_fail (GTK_IS_ENTRY (entry), FALSE); str = gtk_entry_get_text (entry); if (!_device_entry_parse (str, &first, &second)) { g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("can’t parse device name")); goto invalid; } if (first) { if (nm_utils_hwaddr_valid (first, nm_utils_hwaddr_len (type))) mac_tmp = first; else if (!check_ifname || nm_utils_is_valid_iface_name (first, error)) ifname_tmp = first; else goto invalid; } if (second) { if (nm_utils_hwaddr_valid (second, nm_utils_hwaddr_len (type))) { if (!mac_tmp) { mac_tmp = second; } else { g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid hardware address")); goto invalid; } } else if (!check_ifname || nm_utils_is_valid_iface_name (second, error)) { if (!ifname_tmp) ifname_tmp = second; else goto invalid; } else goto invalid; } if (ifname) *ifname = g_strdup (ifname_tmp); if (mac) *mac = g_strdup (mac_tmp); return TRUE; invalid: if (error) { g_prefix_error (error, _("invalid %s (%s): "), device_name ? device_name : _("device"), str); } else { g_set_error (error, NMA_ERROR, NMA_ERROR_GENERIC, _("invalid %s (%s) "), device_name ? device_name : _("device"), str); } return FALSE; } char * ce_page_get_next_available_name (const GPtrArray *connections, const char *format) { GSList *names = NULL, *iter; char *cname = NULL; int i = 0; for (i = 0; i < connections->len; i++) { const char *id; id = nm_connection_get_id (connections->pdata[i]); g_assert (id); names = g_slist_append (names, (gpointer) id); } /* Find the next available unique connection name */ for (i = 1; !cname && i < 10000; i++) { char *temp; gboolean found = FALSE; NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral") temp = g_strdup_printf (format, i); NM_PRAGMA_WARNING_REENABLE for (iter = names; iter; iter = g_slist_next (iter)) { if (!strcmp (iter->data, temp)) { found = TRUE; break; } } if (!found) cname = temp; else g_free (temp); } g_slist_free (names); return cname; } void ce_page_complete_init (CEPage *self, const char *setting_name, GVariant *secrets, GError *error) { GError *update_error = NULL; GVariant *setting_dict; char *dbus_err; g_return_if_fail (self != NULL); g_return_if_fail (CE_IS_PAGE (self)); if (error) { /* Ignore missing settings errors */ dbus_err = g_dbus_error_get_remote_error (error); if ( g_strcmp0 (dbus_err, "org.freedesktop.NetworkManager.Settings.InvalidSetting") == 0 || g_strcmp0 (dbus_err, "org.freedesktop.NetworkManager.Settings.Connection.SettingNotFound") == 0 || g_strcmp0 (dbus_err, "org.freedesktop.NetworkManager.AgentManager.NoSecrets") == 0) g_clear_error (&error); g_free (dbus_err); } if (error) { g_warning ("Couldn't fetch secrets: %s", error->message); g_error_free (error); goto out; } if (!setting_name || !secrets || g_variant_n_children (secrets) == 0) { /* Success, no secrets */ goto out; } g_assert (setting_name); g_assert (secrets); setting_dict = g_variant_lookup_value (secrets, setting_name, NM_VARIANT_TYPE_SETTING); if (!setting_dict) { /* Success, no secrets */ goto out; } g_variant_unref (setting_dict); /* Update the connection with the new secrets */ if (!nm_connection_update_secrets (self->connection, setting_name, secrets, &update_error)) { g_warning ("Couldn't update the secrets: %s", update_error->message); g_error_free (update_error); goto out; } out: g_signal_emit (self, signals[INITIALIZED], 0, NULL); } static void ce_page_init (CEPage *self) { self->builder = gtk_builder_new (); } static void dispose (GObject *object) { CEPage *self = CE_PAGE (object); g_clear_object (&self->page); g_clear_object (&self->builder); g_clear_object (&self->connection); G_OBJECT_CLASS (ce_page_parent_class)->dispose (object); } static void finalize (GObject *object) { CEPage *self = CE_PAGE (object); g_free (self->title); G_OBJECT_CLASS (ce_page_parent_class)->finalize (object); } GtkWidget * ce_page_get_page (CEPage *self) { g_return_val_if_fail (CE_IS_PAGE (self), NULL); return self->page; } const char * ce_page_get_title (CEPage *self) { g_return_val_if_fail (CE_IS_PAGE (self), NULL); return self->title; } void ce_page_changed (CEPage *self) { g_return_if_fail (CE_IS_PAGE (self)); g_signal_emit (self, signals[CHANGED], 0); } NMConnectionEditor * ce_page_new_editor (CEPage *self, GtkWindow *parent_window, NMConnection *connection) { NMConnectionEditor *editor; g_return_val_if_fail (CE_IS_PAGE (self), NULL); editor = nm_connection_editor_new (parent_window, connection, self->client); if (!editor) return NULL; g_signal_emit (self, signals[NEW_EDITOR], 0, editor); return editor; } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CEPage *self = CE_PAGE (object); switch (prop_id) { case PROP_CONNECTION: g_value_set_object (value, self->connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { CEPage *self = CE_PAGE (object); switch (prop_id) { case PROP_CONNECTION: if (self->connection) g_object_unref (self->connection); self->connection = g_value_dup_object (value); break; case PROP_PARENT_WINDOW: self->parent_window = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void ce_page_class_init (CEPageClass *page_class) { GObjectClass *object_class = G_OBJECT_CLASS (page_class); /* virtual methods */ object_class->dispose = dispose; object_class->finalize = finalize; object_class->get_property = get_property; object_class->set_property = set_property; /* Properties */ g_object_class_install_property (object_class, PROP_CONNECTION, g_param_spec_object (CE_PAGE_CONNECTION, "Connection", "Connection", NM_TYPE_CONNECTION, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_PARENT_WINDOW, g_param_spec_pointer (CE_PAGE_PARENT_WINDOW, "Parent window", "Parent window", G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); /* Signals */ signals[CHANGED] = g_signal_new ("changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[INITIALIZED] = g_signal_new (CE_PAGE_INITIALIZED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[NEW_EDITOR] = g_signal_new (CE_PAGE_NEW_EDITOR, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_POINTER); } void ce_page_complete_connection (NMConnection *connection, const char *format, const char *ctype, gboolean autoconnect, NMClient *client) { NMSettingConnection *s_con; char *id, *uuid; const GPtrArray *connections; s_con = nm_connection_get_setting_connection (connection); if (!s_con) { s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); nm_connection_add_setting (connection, NM_SETTING (s_con)); } if (!nm_setting_connection_get_id (s_con)) { connections = nm_client_get_connections (client); id = ce_page_get_next_available_name (connections, format); g_object_set (s_con, NM_SETTING_CONNECTION_ID, id, NULL); g_free (id); } uuid = nm_utils_uuid_generate (); g_object_set (s_con, NM_SETTING_CONNECTION_UUID, uuid, NM_SETTING_CONNECTION_TYPE, ctype, NM_SETTING_CONNECTION_AUTOCONNECT, autoconnect, NULL); g_free (uuid); } CEPage * ce_page_new (GType page_type, NMConnectionEditor *editor, NMConnection *connection, GtkWindow *parent_window, NMClient *client, const char *ui_resource, const char *widget_name, const char *title) { CEPage *self; GError *error = NULL; g_return_val_if_fail (title != NULL, NULL); if (ui_resource) g_return_val_if_fail (widget_name != NULL, NULL); self = CE_PAGE (g_object_new (page_type, CE_PAGE_CONNECTION, connection, CE_PAGE_PARENT_WINDOW, parent_window, NULL)); self->title = g_strdup (title); self->client = client; self->editor = editor; if (ui_resource) { if (!gtk_builder_add_from_resource (self->builder, ui_resource, &error)) { g_warning ("Couldn't load builder resource: %s", error->message); g_error_free (error); g_object_unref (self); return NULL; } self->page = GTK_WIDGET (gtk_builder_get_object (self->builder, widget_name)); if (!self->page) { g_warning ("Couldn't load page widget '%s' from %s", widget_name, ui_resource); g_object_unref (self); return NULL; } g_object_ref_sink (self->page); } return self; }