diff options
| author | Michael Biebl <biebl@debian.org> | 2017-01-17 20:25:09 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2017-01-17 20:25:09 +0100 |
| commit | 58f8be580039b0575b197b9573a1c92745d96d30 (patch) | |
| tree | 2c226233f623a0dcb529be0eb8cdf97e4a2ae0c0 /src/settings/plugins/keyfile/nms-keyfile-writer.c | |
| parent | 45cb5bb3c0e6edb887cf69b417fcaf7053814a9b (diff) | |
New upstream version 1.5.90 upstream/1.5.90
Diffstat (limited to 'src/settings/plugins/keyfile/nms-keyfile-writer.c')
| -rw-r--r-- | src/settings/plugins/keyfile/nms-keyfile-writer.c | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c new file mode 100644 index 00000000..95897db3 --- /dev/null +++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c @@ -0,0 +1,328 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager system settings service - keyfile plugin + * + * 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 (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2015 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nms-keyfile-writer.h" + +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#include "nm-keyfile-internal.h" + +#include "nms-keyfile-utils.h" + +/*****************************************************************************/ + +typedef struct { + const char *keyfile_dir; +} WriteInfo; + +static void +cert_writer (NMConnection *connection, + GKeyFile *file, + NMKeyfileWriteTypeDataCert *cert_data, + WriteInfo *info, + GError **error) +{ + const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting)); + NMSetting8021xCKScheme scheme; + NMSetting8021xCKFormat format; + const char *path = NULL, *ext = "pem"; + + scheme = cert_data->scheme_func (cert_data->setting); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + char *tmp = NULL; + const char *accepted_path = NULL; + + path = cert_data->path_func (cert_data->setting); + g_assert (path); + + if (g_str_has_prefix (path, info->keyfile_dir)) { + const char *p = path + strlen (info->keyfile_dir); + + /* If the path is rooted in the keyfile directory, just use a + * relative path instead of an absolute one. + */ + if (*p == '/') { + while (*p == '/') + p++; + if (p[0]) { + /* If @p looks like an integer list, the following detection will fail too and + * we will file:// qualify the path below. We thus avoid writing a path string + * that would be interpreted as legacy binary format by reader. */ + tmp = nm_keyfile_detect_unqualified_path_scheme (info->keyfile_dir, p, -1, FALSE, NULL); + if (tmp) { + g_clear_pointer (&tmp, g_free); + accepted_path = p; + } + } + } + } + if (!accepted_path) { + /* What we are about to write, must also be understood by the reader. + * Otherwise, add a file:// prefix */ + tmp = nm_keyfile_detect_unqualified_path_scheme (info->keyfile_dir, path, -1, FALSE, NULL); + if (tmp) { + g_clear_pointer (&tmp, g_free); + accepted_path = path; + } + } + + if (!accepted_path) + accepted_path = tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL); + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, accepted_path); + g_free (tmp); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, + cert_data->uri_func (cert_data->setting)); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes *blob; + const guint8 *blob_data; + gsize blob_len; + gboolean success; + GError *local = NULL; + char *new_path; + + blob = cert_data->blob_func (cert_data->setting); + g_assert (blob); + blob_data = g_bytes_get_data (blob, &blob_len); + + if (cert_data->format_func) { + /* Get the extension for a private key */ + format = cert_data->format_func (cert_data->setting); + if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) + ext = "p12"; + } else { + /* DER or PEM format certificate? */ + if (blob_len > 2 && blob_data[0] == 0x30 && blob_data[1] == 0x82) + ext = "der"; + } + + /* Write the raw data out to the standard file so that we can use paths + * from now on instead of pushing around the certificate data. + */ + new_path = g_strdup_printf ("%s/%s-%s.%s", info->keyfile_dir, nm_connection_get_uuid (connection), + cert_data->suffix, ext); + + success = nm_utils_file_set_contents (new_path, (const gchar *) blob_data, + blob_len, 0600, &local); + if (success) { + /* Write the path value to the keyfile. + * We know, that basename(new_path) starts with a UUID, hence no conflict with "data:;base64," */ + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, strrchr (new_path, '/') + 1); + } else { + nm_log_warn (LOGD_SETTINGS, "keyfile: %s.%s: failed to write certificate to file %s: %s", + setting_name, cert_data->property_name, new_path, local->message); + g_error_free (local); + } + g_free (new_path); + } else { + /* scheme_func() returns UNKNOWN in all other cases. The only valid case + * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this + * case, we don't expect the writer to be called, because the default value + * will not be serialized. + * The only other reason for the scheme to be UNKNOWN is an invalid cert. + * But our connection verifies, so that cannot happen either. */ + g_return_if_reached (); + } +} + +static gboolean +_handler_write (NMConnection *connection, + GKeyFile *keyfile, + NMKeyfileWriteType type, + void *type_data, + void *user_data, + GError **error) +{ + if (type == NM_KEYFILE_WRITE_TYPE_CERT) { + cert_writer (connection, keyfile, + (NMKeyfileWriteTypeDataCert *) type_data, + (WriteInfo *) user_data, error); + return TRUE; + } + return FALSE; +} + +static gboolean +_internal_write_connection (NMConnection *connection, + const char *keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + const char *existing_path, + gboolean force_rename, + char **out_path, + GError **error) +{ + GKeyFile *key_file; + gs_free char *data = NULL; + gsize len; + gs_free char *path = NULL; + const char *id; + WriteInfo info = { 0 }; + GError *local_err = NULL; + int errsv; + + g_return_val_if_fail (!out_path || !*out_path, FALSE); + g_return_val_if_fail (keyfile_dir && keyfile_dir[0] == '/', FALSE); + + if (!nm_connection_verify (connection, error)) + g_return_val_if_reached (FALSE); + + id = nm_connection_get_id (connection); + g_assert (id && *id); + + info.keyfile_dir = keyfile_dir; + + key_file = nm_keyfile_write (connection, _handler_write, &info, error); + if (!key_file) + return FALSE; + data = g_key_file_to_data (key_file, &len, error); + g_key_file_unref (key_file); + if (!data) + return FALSE; + + /* If we have existing file path, use it. Else generate one from + * connection's ID. + */ + if (existing_path != NULL && !force_rename) { + path = g_strdup (existing_path); + } else { + char *filename_escaped = nms_keyfile_utils_escape_filename (id); + + path = g_build_filename (keyfile_dir, filename_escaped, NULL); + g_free (filename_escaped); + } + + /* If a file with this path already exists (but isn't the existing path + * of the connection) then we need another name. Multiple connections + * can have the same ID (ie if two connections with the same ID are visible + * to different users) but of course can't have the same path. Yeah, + * there's a race here, but there's not a lot we can do about it, and + * we shouldn't get more than one connection with the same UUID either. + */ + if (g_strcmp0 (path, existing_path) != 0 && g_file_test (path, G_FILE_TEST_EXISTS)) { + guint i; + gboolean name_found = FALSE; + + /* A keyfile with this connection's ID already exists. Pick another name. */ + for (i = 0; i < 100; i++) { + char *filename, *filename_escaped; + + if (i == 0) + filename = g_strdup_printf ("%s-%s", id, nm_connection_get_uuid (connection)); + else + filename = g_strdup_printf ("%s-%s-%u", id, nm_connection_get_uuid (connection), i); + + filename_escaped = nms_keyfile_utils_escape_filename (filename); + + g_free (path); + path = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped); + g_free (filename); + g_free (filename_escaped); + if (g_strcmp0 (path, existing_path) == 0 || !g_file_test (path, G_FILE_TEST_EXISTS)) { + name_found = TRUE; + break; + } + } + if (!name_found) { + if (existing_path == NULL) { + /* this really should not happen, we tried hard to find an unused name... bail out. */ + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "could not find suitable keyfile file name (%s already used)", path); + return FALSE; + } + /* Both our preferred path based on connection id and id-uuid are taken. + * Fallback to @existing_path */ + g_free (path); + path = g_strdup (existing_path); + } + } + + /* In case of updating the connection and changing the file path, + * we need to remove the old one, not to end up with two connections. + */ + if (existing_path != NULL && strcmp (path, existing_path) != 0) + unlink (existing_path); + + nm_utils_file_set_contents (path, data, len, 0600, &local_err); + if (local_err) { + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "error writing to file '%s': %s", + path, local_err->message); + g_error_free (local_err); + return FALSE; + } + + if (chown (path, owner_uid, owner_grp) < 0) { + errsv = errno; + g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, + "error chowning '%s': %s (%d)", + path, g_strerror (errsv), errsv); + unlink (path); + return FALSE; + } + + if (out_path && g_strcmp0 (existing_path, path)) { + *out_path = path; /* pass path out to caller */ + path = NULL; + } + + return TRUE; +} + +gboolean +nms_keyfile_writer_connection (NMConnection *connection, + const char *existing_path, + gboolean force_rename, + char **out_path, + GError **error) +{ + return _internal_write_connection (connection, + nms_keyfile_utils_get_path (), + 0, 0, + existing_path, + force_rename, + out_path, + error); +} + +gboolean +nms_keyfile_writer_test_connection (NMConnection *connection, + const char *keyfile_dir, + uid_t owner_uid, + pid_t owner_grp, + char **out_path, + GError **error) +{ + return _internal_write_connection (connection, + keyfile_dir, + owner_uid, owner_grp, + NULL, + FALSE, + out_path, + error); +} + |