gnome-control-center/panels/network/wireless-security/eap-method.c
Georges Basile Stavracas Neto efbad6eb50 network: Port to GTK4
Boy this was hard.

To ease the pain of porting wireless-security to GTK4, add
a new WsFileChooserButton class that mimics the behavior of
a button that triggers a filechooser, as per the migration
guide suggests.

There were lots of GtkGrids, so the diff is particularly
horrendous. Sorry.

This needs serious testing before landing.
2021-12-14 22:34:21 -03:00

583 lines
16 KiB
C

/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */
/* NetworkManager Applet -- allow user control over networking
*
* Dan Williams <dcbw@redhat.com>
*
* 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.
*
* Copyright 2007 - 2014 Red Hat, Inc.
*/
#include <fcntl.h>
#include <glib/gi18n.h>
#include "eap-method.h"
#include "helpers.h"
#include "ui-helpers.h"
G_DEFINE_INTERFACE (EAPMethod, eap_method, G_TYPE_OBJECT)
enum {
CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
eap_method_default_init (EAPMethodInterface *iface)
{
signals[CHANGED] =
g_signal_new ("changed",
G_TYPE_FROM_INTERFACE (iface),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
GtkWidget *
eap_method_get_default_field (EAPMethod *self)
{
g_return_val_if_fail (self != NULL, NULL);
return EAP_METHOD_GET_IFACE (self)->get_default_field (self);
}
const gchar *
eap_method_get_password_flags_name (EAPMethod *self)
{
g_return_val_if_fail (self != NULL, NULL);
if (EAP_METHOD_GET_IFACE (self)->get_password_flags_name)
return EAP_METHOD_GET_IFACE (self)->get_password_flags_name (self);
else
return NULL;
}
gboolean
eap_method_get_phase2 (EAPMethod *self)
{
g_return_val_if_fail (self != NULL, FALSE);
if (EAP_METHOD_GET_IFACE (self)->get_phase2)
return EAP_METHOD_GET_IFACE (self)->get_phase2 (self);
else
return FALSE;
}
gboolean
eap_method_validate (EAPMethod *self, GError **error)
{
gboolean result;
g_return_val_if_fail (self != NULL, FALSE);
result = (*(EAP_METHOD_GET_IFACE (self)->validate)) (self, error);
if (!result && error && !*error)
g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("undefined error in 802.1X security (wpa-eap)"));
return result;
}
void
eap_method_update_secrets (EAPMethod *self, NMConnection *connection)
{
g_return_if_fail (self != NULL);
if (EAP_METHOD_GET_IFACE (self)->update_secrets)
EAP_METHOD_GET_IFACE (self)->update_secrets (self, connection);
}
void
eap_method_add_to_size_group (EAPMethod *self, GtkSizeGroup *group)
{
g_return_if_fail (self != NULL);
g_return_if_fail (group != NULL);
return (*(EAP_METHOD_GET_IFACE (self)->add_to_size_group)) (self, group);
}
void
eap_method_fill_connection (EAPMethod *self,
NMConnection *connection,
NMSettingSecretFlags flags)
{
g_return_if_fail (self != NULL);
g_return_if_fail (connection != NULL);
return (*(EAP_METHOD_GET_IFACE (self)->fill_connection)) (self, connection, flags);
}
void
eap_method_emit_changed (EAPMethod *self)
{
g_return_if_fail (EAP_IS_METHOD (self));
g_signal_emit (self, signals[CHANGED], 0);
}
const gchar *
eap_method_get_username (EAPMethod *self)
{
g_return_val_if_fail (EAP_IS_METHOD (self), NULL);
return EAP_METHOD_GET_IFACE (self)->get_username (self);
}
void
eap_method_set_username (EAPMethod *self, const gchar *username)
{
g_return_if_fail (EAP_IS_METHOD (self));
EAP_METHOD_GET_IFACE (self)->set_username (self, username);
}
const gchar *
eap_method_get_password (EAPMethod *self)
{
g_return_val_if_fail (EAP_IS_METHOD (self), NULL);
return EAP_METHOD_GET_IFACE (self)->get_password (self);
}
void
eap_method_set_password (EAPMethod *self, const gchar *password)
{
g_return_if_fail (EAP_IS_METHOD (self));
EAP_METHOD_GET_IFACE (self)->set_password (self, password);
}
gboolean
eap_method_get_show_password (EAPMethod *self)
{
g_return_val_if_fail (EAP_IS_METHOD (self), FALSE);
return EAP_METHOD_GET_IFACE (self)->get_show_password (self);
}
void
eap_method_set_show_password (EAPMethod *self, gboolean show_password)
{
g_return_if_fail (EAP_IS_METHOD (self));
EAP_METHOD_GET_IFACE (self)->set_show_password (self, show_password);
}
gboolean
eap_method_validate_filepicker (GtkFileChooser *chooser,
guint32 item_type,
const char *password,
NMSetting8021xCKFormat *out_format,
GError **error)
{
g_autofree gchar *filename = NULL;
g_autoptr(NMSetting8021x) setting = NULL;
g_autoptr(GFile) file = NULL;
gboolean success = TRUE;
if (item_type == TYPE_PRIVATE_KEY) {
if (!password || *password == '\0')
success = FALSE;
}
file = gtk_file_chooser_get_file (chooser);
if (!file) {
if (item_type != TYPE_CA_CERT) {
success = FALSE;
g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("no file selected"));
}
goto out;
}
filename = g_file_get_path (file);
if (!g_file_test (filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
success = FALSE;
goto out;
}
setting = (NMSetting8021x *) nm_setting_802_1x_new ();
success = FALSE;
if (item_type == TYPE_PRIVATE_KEY) {
if (nm_setting_802_1x_set_private_key (setting, filename, password, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error))
success = TRUE;
} else if (item_type == TYPE_CLIENT_CERT) {
if (nm_setting_802_1x_set_client_cert (setting, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error))
success = TRUE;
} else if (item_type == TYPE_CA_CERT) {
if (nm_setting_802_1x_set_ca_cert (setting, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, error))
success = TRUE;
} else
g_warning ("%s: invalid item type %d.", __func__, item_type);
out:
if (!success && error && !*error)
g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("unspecified error validating eap-method file"));
if (success)
widget_unset_error (GTK_WIDGET (chooser));
else
widget_set_error (GTK_WIDGET (chooser));
return success;
}
static gboolean
file_has_extension (const char *filename, const char *extensions[])
{
char *p;
g_autofree gchar *ext = NULL;
int i = 0;
gboolean found = FALSE;
p = strrchr (filename, '.');
if (!p)
return FALSE;
ext = g_ascii_strdown (p, -1);
if (ext) {
while (extensions[i]) {
if (!strcmp (ext, extensions[i++])) {
found = TRUE;
break;
}
}
}
return found;
}
#if !LIBNM_BUILD
static const char *
find_tag (const char *tag, const char *buf, gsize len)
{
gsize i, taglen;
taglen = strlen (tag);
if (len < taglen)
return NULL;
for (i = 0; i < len - taglen + 1; i++) {
if (memcmp (buf + i, tag, taglen) == 0)
return buf + i;
}
return NULL;
}
static const char *pem_rsa_key_begin = "-----BEGIN RSA PRIVATE KEY-----";
static const char *pem_dsa_key_begin = "-----BEGIN DSA PRIVATE KEY-----";
static const char *pem_pkcs8_enc_key_begin = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
static const char *pem_pkcs8_dec_key_begin = "-----BEGIN PRIVATE KEY-----";
static const char *pem_cert_begin = "-----BEGIN CERTIFICATE-----";
static const char *proc_type_tag = "Proc-Type: 4,ENCRYPTED";
static const char *dek_info_tag = "DEK-Info:";
static gboolean
pem_file_is_encrypted (const char *buffer, gsize bytes_read)
{
/* Check if the private key is encrypted or not by looking for the
* old OpenSSL-style proc-type and dec-info tags.
*/
if (find_tag (proc_type_tag, (const char *) buffer, bytes_read)) {
if (find_tag (dek_info_tag, (const char *) buffer, bytes_read))
return TRUE;
}
return FALSE;
}
static gboolean
file_is_der_or_pem (const char *filename,
gboolean privkey,
gboolean *out_privkey_encrypted)
{
int fd;
unsigned char buffer[8192];
ssize_t bytes_read;
gboolean success = FALSE;
fd = open (filename, O_RDONLY);
if (fd < 0)
return FALSE;
bytes_read = read (fd, buffer, sizeof (buffer) - 1);
if (bytes_read < 400) /* needs to be lower? */
goto out;
buffer[bytes_read] = '\0';
/* Check for DER signature */
if (bytes_read > 2 && buffer[0] == 0x30 && buffer[1] == 0x82) {
success = TRUE;
goto out;
}
/* Check for PEM signatures */
if (privkey) {
if (find_tag (pem_rsa_key_begin, (const char *) buffer, bytes_read)) {
success = TRUE;
if (out_privkey_encrypted)
*out_privkey_encrypted = pem_file_is_encrypted ((const char *) buffer, bytes_read);
goto out;
}
if (find_tag (pem_dsa_key_begin, (const char *) buffer, bytes_read)) {
success = TRUE;
if (out_privkey_encrypted)
*out_privkey_encrypted = pem_file_is_encrypted ((const char *) buffer, bytes_read);
goto out;
}
if (find_tag (pem_pkcs8_enc_key_begin, (const char *) buffer, bytes_read)) {
success = TRUE;
if (out_privkey_encrypted)
*out_privkey_encrypted = TRUE;
goto out;
}
if (find_tag (pem_pkcs8_dec_key_begin, (const char *) buffer, bytes_read)) {
success = TRUE;
if (out_privkey_encrypted)
*out_privkey_encrypted = FALSE;
goto out;
}
} else {
if (find_tag (pem_cert_begin, (const char *) buffer, bytes_read)) {
success = TRUE;
goto out;
}
}
out:
close (fd);
return success;
}
#endif
static const char *privkey_extensions[] = {
".der", ".pem", ".p12", ".key", NULL
};
static const char *cert_extensions[] = {
".der", ".pem", ".crt", ".cer", NULL
};
static void
add_file_extensions_to_filter (GtkFileFilter *filter,
const char **extensions)
{
int i;
for (i = 0; extensions[i] != NULL; i++)
gtk_file_filter_add_suffix (filter, extensions[i]);
}
GtkFileFilter *
eap_method_default_file_chooser_filter_new (gboolean privkey)
{
GtkFileFilter *filter;
filter = gtk_file_filter_new ();
if (privkey) {
add_file_extensions_to_filter (filter, privkey_extensions);
gtk_file_filter_set_name (filter, _("DER, PEM, or PKCS#12 private keys (*.der, *.pem, *.p12, *.key)"));
} else {
add_file_extensions_to_filter (filter, cert_extensions);
gtk_file_filter_set_name (filter, _("DER or PEM certificates (*.der, *.pem, *.crt, *.cer)"));
}
return filter;
}
gboolean
eap_method_is_encrypted_private_key (const char *path)
{
gboolean is_encrypted;
if (!file_has_extension (path, privkey_extensions))
return FALSE;
#if LIBNM_BUILD
is_encrypted = FALSE;
if (!nm_utils_file_is_private_key (path, &is_encrypted))
return FALSE;
#else
is_encrypted = TRUE;
if ( !file_is_der_or_pem (path, TRUE, &is_encrypted)
&& !nm_utils_file_is_pkcs12 (path))
return FALSE;
#endif
return is_encrypted;
}
void
eap_method_ca_cert_not_required_toggled (GtkCheckButton *id_ca_cert_not_required_checkbutton, GtkFileChooser *id_ca_cert_chooser)
{
g_autoptr(GFile) file = NULL;
g_autoptr(GFile) file_old = NULL;
gboolean is_not_required;
g_assert (id_ca_cert_not_required_checkbutton && id_ca_cert_chooser);
is_not_required = gtk_check_button_get_active (id_ca_cert_not_required_checkbutton);
file = gtk_file_chooser_get_file (id_ca_cert_chooser);
file_old = g_object_steal_data (G_OBJECT (id_ca_cert_chooser), "filename-old");
if (is_not_required) {
g_clear_object (&file_old);
file_old = g_steal_pointer (&file);
} else {
g_clear_object (&file);
file = g_steal_pointer (&file_old);
}
gtk_widget_set_sensitive (GTK_WIDGET (id_ca_cert_chooser), !is_not_required);
if (file)
gtk_file_chooser_set_file (id_ca_cert_chooser, file, NULL);
g_object_set_data_full (G_OBJECT (id_ca_cert_chooser), "filename-old", g_steal_pointer (&file_old), g_free);
}
/* Used as both GSettings keys and GObject data tags */
#define IGNORE_CA_CERT_TAG "ignore-ca-cert"
#define IGNORE_PHASE2_CA_CERT_TAG "ignore-phase2-ca-cert"
/**
* eap_method_ca_cert_ignore_set:
* @method: the #EAPMethod object
* @connection: the #NMConnection
* @filename: the certificate file, if any
* @ca_cert_error: %TRUE if an error was encountered loading the given CA
* certificate, %FALSE if not or if a CA certificate is not present
*
* Updates the connection's CA cert ignore value to %TRUE if the "CA certificate
* not required" checkbox is checked. If @ca_cert_error is %TRUE, then the
* connection's CA cert ignore value will always be set to %FALSE, because it
* means that the user selected an invalid certificate (thus he does not want to
* ignore the CA cert)..
*/
void
eap_method_ca_cert_ignore_set (EAPMethod *self,
NMConnection *connection,
const char *filename,
gboolean ca_cert_error)
{
NMSetting8021x *s_8021x;
gboolean ignore;
s_8021x = nm_connection_get_setting_802_1x (connection);
if (s_8021x) {
ignore = !ca_cert_error && filename == NULL;
g_object_set_data (G_OBJECT (s_8021x),
eap_method_get_phase2 (self) ? IGNORE_PHASE2_CA_CERT_TAG : IGNORE_CA_CERT_TAG,
GUINT_TO_POINTER (ignore));
}
}
/**
* eap_method_ca_cert_ignore_get:
* @method: the #EAPMethod object
* @connection: the #NMConnection
*
* Returns: %TRUE if a missing CA certificate can be ignored, %FALSE if a CA
* certificate should be required for the connection to be valid.
*/
gboolean
eap_method_ca_cert_ignore_get (EAPMethod *self, NMConnection *connection)
{
NMSetting8021x *s_8021x;
s_8021x = nm_connection_get_setting_802_1x (connection);
if (s_8021x) {
return !!g_object_get_data (G_OBJECT (s_8021x),
eap_method_get_phase2 (self) ? IGNORE_PHASE2_CA_CERT_TAG : IGNORE_CA_CERT_TAG);
}
return FALSE;
}
static GSettings *
_get_ca_ignore_settings (NMConnection *connection)
{
GSettings *settings;
g_autofree gchar *path = NULL;
const char *uuid;
g_return_val_if_fail (connection, NULL);
uuid = nm_connection_get_uuid (connection);
g_return_val_if_fail (uuid && *uuid, NULL);
path = g_strdup_printf ("/org/gnome/nm-applet/eap/%s/", uuid);
settings = g_settings_new_with_path ("org.gnome.nm-applet.eap", path);
return settings;
}
/**
* eap_method_ca_cert_ignore_save:
* @connection: the connection for which to save CA cert ignore values to GSettings
*
* Reads the CA cert ignore tags from the 802.1x setting GObject data and saves
* then to GSettings if present, using the connection UUID as the index.
*/
void
eap_method_ca_cert_ignore_save (NMConnection *connection)
{
NMSetting8021x *s_8021x;
g_autoptr(GSettings) settings = NULL;
gboolean ignore = FALSE, phase2_ignore = FALSE;
g_return_if_fail (connection);
s_8021x = nm_connection_get_setting_802_1x (connection);
if (s_8021x) {
ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_CA_CERT_TAG);
phase2_ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_PHASE2_CA_CERT_TAG);
}
settings = _get_ca_ignore_settings (connection);
if (!settings)
return;
g_settings_set_boolean (settings, IGNORE_CA_CERT_TAG, ignore);
g_settings_set_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG, phase2_ignore);
}
/**
* eap_method_ca_cert_ignore_load:
* @connection: the connection for which to load CA cert ignore values to GSettings
*
* Reads the CA cert ignore tags from the 802.1x setting GObject data and saves
* then to GSettings if present, using the connection UUID as the index.
*/
void
eap_method_ca_cert_ignore_load (NMConnection *connection)
{
g_autoptr(GSettings) settings = NULL;
NMSetting8021x *s_8021x;
gboolean ignore, phase2_ignore;
g_return_if_fail (connection);
s_8021x = nm_connection_get_setting_802_1x (connection);
if (!s_8021x)
return;
settings = _get_ca_ignore_settings (connection);
if (!settings)
return;
ignore = g_settings_get_boolean (settings, IGNORE_CA_CERT_TAG);
phase2_ignore = g_settings_get_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG);
g_object_set_data (G_OBJECT (s_8021x),
IGNORE_CA_CERT_TAG,
GUINT_TO_POINTER (ignore));
g_object_set_data (G_OBJECT (s_8021x),
IGNORE_PHASE2_CA_CERT_TAG,
GUINT_TO_POINTER (phase2_ignore));
}