summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2018-10-01 00:42:12 +0200
committerThomas Haller <thaller@redhat.com>2018-11-29 08:09:20 +0100
commitc12b077c8c8a627192430ec0e63a4d772a067383 (patch)
tree3b45fdb43a7dd653b581b0fac5bee6f0645518e8
parent37f49dc48397616d2b90358361cb236673740afe (diff)
downloadNetworkManager-th/keyfile-loaded-uuid.tar.gz
keyfile: add helper functions to record loaded UUID filesth/keyfile-loaded-uuid
This code will be used later. We want to remember which keyfiles are currently loaded (or hidden). With the addition or multiple keyfile directories (soon), there are two cases where this matters: - if there are multiple keyfiles which reference the same UUID, we can only load one of them. That is already a problem today with only one keyfile directory, where multiple files can reference the same UUID. The implementation will pick the file based on priorities (like the file modification date). However, the user may call explicitly call `nmcli connection load`. In that case, we cannot reload all files to find out whether the to be loaded file is hidden according to the defined priorities. We cannot do that, because we must not make decisions based on files on disk, which we are not told to reload. So, during a `nmcli connection load` we must look at unrelated files, to determine how to load the file. Instead, we do allow the user to load any file, even if it would be shadowed by other files. When we do that, we may want to persist which file is currently loaded, so that a service restart and a `nmcli connection reload` does not undo the load again. This can be later later be solved by writing a symlink "/var/run/NetworkManager/system-connections/.loaded-$UUID.nmkeyfile" which targets the currently active file. - if a profile was loaded from read-only persistant storage, the user may still delete the profile. We also need to remember the deletion of the file. That will be achieved by symlinking "/dev/null" as "/etc/NetworkManager/system-connections/.loaded-$UUID.nmkeyfile". Add helper functions to read and write these symlinks.
-rw-r--r--libnm-core/nm-keyfile-internal.h4
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c3
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.c238
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.h37
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile.c105
5 files changed, 369 insertions, 18 deletions
diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h
index 98df586596..5487f59da3 100644
--- a/libnm-core/nm-keyfile-internal.h
+++ b/libnm-core/nm-keyfile-internal.h
@@ -175,6 +175,10 @@ gboolean _nm_keyfile_has_values (GKeyFile *keyfile);
#define NM_KEYFILE_PATH_SUFFIX_NMCONNECTION ".nmconnection"
+#define NM_KEYFILE_PATH_PREFIX_NMLOADED ".loaded-"
+
+#define NM_KEYFILE_PATH_NMLOADED_NULL "/dev/null"
+
gboolean nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension);
char *nm_keyfile_utils_create_filename (const char *filename, gboolean with_extension);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index d9d796e726..314b103392 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -172,7 +172,8 @@ nms_keyfile_reader_from_file (const char *full_filename,
nm_assert (full_filename && full_filename[0] == '/');
nm_assert (!profile_dir || profile_dir[0] == '/');
- if (!nms_keyfile_utils_check_file_permissions (full_filename,
+ if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_KEYFILE,
+ full_filename,
NULL,
error))
return NULL;
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/settings/plugins/keyfile/nms-keyfile-utils.c
index 98eecd7ca7..8d4ec943c4 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -27,6 +27,7 @@
#include <sys/stat.h>
#include "nm-keyfile-internal.h"
+#include "nm-utils.h"
#include "nm-setting-wired.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wireless-security.h"
@@ -34,18 +35,213 @@
/*****************************************************************************/
+char *
+nms_keyfile_loaded_uuid_filename (const char *dirname,
+ const char *uuid,
+ gboolean temporary)
+{
+ char filename[250];
+
+ nm_assert (dirname && dirname[0] == '/');
+ nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+
+ if (g_snprintf (filename,
+ sizeof (filename),
+ "%s%s%s%s",
+ NM_KEYFILE_PATH_PREFIX_NMLOADED,
+ uuid,
+ NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
+ temporary ? "~" : "") >= sizeof (filename)) {
+ /* valid uuids are limited in length. The buffer should always be large
+ * enough. */
+ nm_assert_not_reached ();
+ return NULL;
+ }
+
+ return g_build_filename (dirname, filename, NULL);
+}
+
gboolean
-nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
- GError **error)
+nms_keyfile_loaded_uuid_read (const char *dirname,
+ const char *filename,
+ char **out_full_filename,
+ char **out_uuid,
+ char **out_loaded_path)
{
- g_return_val_if_fail (st, FALSE);
+ const char *uuid;
+ const char *tmp;
+ gsize len;
+ gs_free char *full_filename = NULL;
+ gs_free char *ln = NULL;
- if (!S_ISREG (st->st_mode)) {
- g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
- "file is not a regular file");
+ nm_assert (dirname && dirname[0] == '/');
+ nm_assert (filename && filename[0] && !strchr (filename, '/'));
+
+ if (filename[0] != '.') {
+ /* the hidden-uuid filename must start with '.'. That is,
+ * so that it does not conflict with regular keyfiles according
+ * to nm_keyfile_utils_ignore_filename(). */
return FALSE;
}
+ len = strlen (filename);
+ if ( len <= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)
+ || memcmp (filename, NM_KEYFILE_PATH_PREFIX_NMLOADED, NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)) != 0) {
+ /* the filename does not have the right prefix. */
+ return FALSE;
+ }
+
+ tmp = &filename[NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)];
+ len -= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED);
+
+ if ( len <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
+ || memcmp (&tmp[len - NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)],
+ NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
+ NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) != 0) {
+ /* the file does not have the right suffix. */
+ return FALSE;
+ }
+ len -= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+
+ if (!NM_IN_SET (len, 36, 40)) {
+ /* the remaining part of the filename has not the right length to
+ * contain a UUID (according to nm_utils_is_uuid()). */
+ return FALSE;
+ }
+
+ uuid = nm_strndup_a (100, tmp, len, NULL);
+ if (!nm_utils_is_uuid (uuid))
+ return FALSE;
+
+ full_filename = g_build_filename (dirname, filename, NULL);
+
+ if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMLOADED,
+ full_filename,
+ NULL,
+ NULL))
+ return FALSE;
+
+ ln = nm_utils_read_link_absolute (full_filename, NULL);
+ if (!ln)
+ return FALSE;
+
+ NM_SET_OUT (out_uuid, g_strdup (uuid));
+ NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+ NM_SET_OUT (out_loaded_path, g_steal_pointer (&ln));
+ return TRUE;
+}
+
+gboolean
+nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
+ char **out_dirname,
+ char **out_filename,
+ char **out_uuid,
+ char **out_loaded_path)
+{
+ gs_free char *dirname = NULL;
+ gs_free char *filename = NULL;
+
+ nm_assert (full_filename && full_filename[0] == '/');
+
+ filename = g_path_get_basename (full_filename);
+ dirname = g_path_get_dirname (full_filename);
+
+ if (!nms_keyfile_loaded_uuid_read (dirname,
+ filename,
+ NULL,
+ out_uuid,
+ out_loaded_path))
+ return FALSE;
+
+ NM_SET_OUT (out_dirname, g_steal_pointer (&dirname));
+ NM_SET_OUT (out_filename, g_steal_pointer (&filename));
+ return TRUE;
+}
+
+gboolean
+nms_keyfile_loaded_uuid_write (const char *dirname,
+ const char *uuid,
+ const char *loaded_path,
+ gboolean allow_relative,
+ char **out_full_filename)
+{
+ gs_free char *full_filename_tmp = NULL;
+ gs_free char *full_filename = NULL;
+
+ nm_assert (dirname && dirname[0] == '/');
+ nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+ nm_assert (!loaded_path || loaded_path[0] == '/');
+
+ full_filename_tmp = nms_keyfile_loaded_uuid_filename (dirname, uuid, TRUE);
+
+ nm_assert (g_str_has_suffix (full_filename_tmp, "~"));
+ nm_assert (nm_utils_file_is_in_path (full_filename_tmp, dirname));
+
+ (void) unlink (full_filename_tmp);
+
+ if (!loaded_path) {
+ gboolean success = TRUE;
+
+ full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0';
+ if (unlink (full_filename_tmp) != 0)
+ success = NM_IN_SET (errno, ENOENT);
+ NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+ return success;
+ }
+
+ if (allow_relative) {
+ const char *f;
+
+ f = nm_utils_file_is_in_path (loaded_path, dirname);
+ if (f) {
+ /* @loaded_path points to a file directly in @dirname.
+ * Don't use absolute paths. */
+ loaded_path = f;
+ }
+ }
+
+ if (symlink (loaded_path, full_filename_tmp) != 0) {
+ full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0';
+ NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+ return FALSE;
+ }
+
+ full_filename = g_strdup (full_filename_tmp);
+ full_filename[strlen (full_filename) - 1] = '\0';
+ if (rename (full_filename_tmp, full_filename) != 0) {
+ (void) unlink (full_filename_tmp);
+ NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+ return FALSE;
+ }
+
+ NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+gboolean
+nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
+ const struct stat *st,
+ GError **error)
+{
+ g_return_val_if_fail (st, FALSE);
+
+ if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+ if (!S_ISREG (st->st_mode)) {
+ g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+ "file is not a regular file");
+ return FALSE;
+ }
+ } else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
+ if (!S_ISLNK (st->st_mode)) {
+ g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+ "file is not a slink");
+ return FALSE;
+ }
+ } else
+ g_return_val_if_reached (FALSE);
+
if (!NM_FLAGS_HAS (nm_utils_get_testing (), NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK)) {
if (st->st_uid != 0) {
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -54,7 +250,8 @@ nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
return FALSE;
}
- if (st->st_mode & 0077) {
+ if ( filetype == NMS_KEYFILE_FILETYPE_KEYFILE
+ && (st->st_mode & 0077)) {
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"File permissions (%03o) are insecure",
st->st_mode);
@@ -66,7 +263,8 @@ nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
}
gboolean
-nms_keyfile_utils_check_file_permissions (const char *filename,
+nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
+ const char *filename,
struct stat *out_st,
GError **error)
{
@@ -75,14 +273,24 @@ nms_keyfile_utils_check_file_permissions (const char *filename,
g_return_val_if_fail (filename && filename[0] == '/', FALSE);
- if (stat (filename, &st) != 0) {
- errsv = errno;
- g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
- "cannot access file: %s", g_strerror (errsv));
- return FALSE;
- }
+ if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+ if (stat (filename, &st) != 0) {
+ errsv = errno;
+ g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+ "cannot access file: %s", g_strerror (errsv));
+ return FALSE;
+ }
+ } else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
+ if (lstat (filename, &st) != 0) {
+ errsv = errno;
+ g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+ "cannot access file: %s", g_strerror (errsv));
+ return FALSE;
+ }
+ } else
+ g_return_val_if_reached (FALSE);
- if (!nms_keyfile_utils_check_file_permissions_stat (&st, error))
+ if (!nms_keyfile_utils_check_file_permissions_stat (filetype, &st, error))
return FALSE;
NM_SET_OUT (out_st, st);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.h b/src/settings/plugins/keyfile/nms-keyfile-utils.h
index a74139c472..bc601dad51 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -29,13 +29,46 @@
#define NMS_KEYFILE_CONNECTION_LOG_FMTD "%s (%s,\"%s\",%p)"
#define NMS_KEYFILE_CONNECTION_LOG_ARGD(con) NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con)), (con)
+typedef enum {
+ NMS_KEYFILE_FILETYPE_KEYFILE,
+ NMS_KEYFILE_FILETYPE_NMLOADED,
+} NMSKeyfileFiletype;
+
const char *nms_keyfile_utils_get_path (void);
+/*****************************************************************************/
+
+char *nms_keyfile_loaded_uuid_filename (const char *dirname,
+ const char *uuid,
+ gboolean temporary);
+
+gboolean nms_keyfile_loaded_uuid_read (const char *dirname,
+ const char *filename,
+ char **out_full_filename,
+ char **out_uuid,
+ char **out_loaded_path);
+
+gboolean nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
+ char **out_dirname,
+ char **out_filename,
+ char **out_uuid,
+ char **out_loaded_path);
+
+gboolean nms_keyfile_loaded_uuid_write (const char *dirname,
+ const char *uuid,
+ const char *loaded_path,
+ gboolean allow_relative,
+ char **out_full_filename);
+
+/*****************************************************************************/
+
struct stat;
-gboolean nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
+gboolean nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
+ const struct stat *st,
GError **error);
-gboolean nms_keyfile_utils_check_file_permissions (const char *filename,
+gboolean nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
+ const char *filename,
struct stat *out_st,
GError **error);
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c
index 2521798ab8..f9d006967e 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile.c
@@ -2511,6 +2511,109 @@ test_nm_keyfile_plugin_utils_escape_filename (void)
/*****************************************************************************/
+static void
+_assert_keyfile_loaded_uuid (const char *dirname,
+ const char *uuid,
+ const char *loaded_path,
+ gboolean allow_relative,
+ const char *exp_full_filename,
+ const char *exp_uuid,
+ const char *exp_symlink_target,
+ const char *exp_loaded_path)
+{
+ gs_free char *full_filename = NULL;
+ gs_free char *symlink_target = NULL;
+ gs_free char *uuid2 = NULL;
+ gs_free char *loaded_path2 = NULL;
+ gs_free char *dirname3 = NULL;
+ gs_free char *filename3 = NULL;
+ gs_free char *uuid3 = NULL;
+ gs_free char *loaded_path3 = NULL;
+ gboolean success;
+ gs_free char *filename = NULL;
+
+ g_assert (dirname && dirname[0] == '/');
+ g_assert (exp_full_filename && exp_full_filename[0]);
+ g_assert (!exp_loaded_path || exp_loaded_path[0] == '/');
+
+ filename = g_path_get_basename (exp_full_filename);
+
+ full_filename = nms_keyfile_loaded_uuid_filename (dirname, uuid, FALSE);
+ g_assert_cmpstr (full_filename, ==, full_filename);
+ nm_clear_g_free (&full_filename);
+
+
+ g_assert (nms_keyfile_loaded_uuid_write (dirname, uuid, loaded_path, allow_relative, &full_filename));
+ g_assert_cmpstr (full_filename, ==, exp_full_filename);
+ nm_clear_g_free (&full_filename);
+
+ if (exp_symlink_target)
+ g_assert (g_file_test (exp_full_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_SYMLINK));
+ else
+ g_assert (!g_file_test (exp_full_filename, G_FILE_TEST_EXISTS));
+ symlink_target = g_file_read_link (exp_full_filename, NULL);
+ g_assert_cmpstr (symlink_target, ==, exp_symlink_target);
+
+
+ success = nms_keyfile_loaded_uuid_read (dirname, filename, &full_filename, &uuid2, &loaded_path2);
+ g_assert_cmpint (!!exp_uuid, ==, success);
+ if (success)
+ g_assert_cmpstr (full_filename, ==, exp_full_filename);
+ else
+ g_assert_cmpstr (full_filename, ==, NULL);
+ nm_clear_g_free (&full_filename);
+ g_assert_cmpstr (uuid2, ==, exp_uuid);
+ g_assert_cmpstr (loaded_path2, ==, exp_loaded_path);
+
+
+ success = nms_keyfile_loaded_uuid_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3);
+ g_assert_cmpint (!!exp_uuid, ==, success);
+ if (success) {
+ g_assert_cmpstr (dirname3, ==, dirname);
+ g_assert_cmpstr (filename3, ==, filename);
+ } else {
+ g_assert_cmpstr (dirname3, ==, NULL);
+ g_assert_cmpstr (filename3, ==, NULL);
+ }
+ g_assert_cmpstr (uuid3, ==, exp_uuid);
+ g_assert_cmpstr (loaded_path3, ==, exp_loaded_path);
+}
+
+static void
+test_loaded_uuid (void)
+{
+ const char *uuid = "3c03fd17-ddc3-4100-a954-88b6fafff959";
+ gs_free char *filename = g_strdup_printf ("%s%s%s",
+ NM_KEYFILE_PATH_PREFIX_NMLOADED,
+ uuid,
+ NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+ gs_free char *full_filename = g_strdup_printf ("%s/%s",
+ TEST_SCRATCH_DIR,
+ filename);
+ const char *loaded_path0 = NM_KEYFILE_PATH_NMLOADED_NULL;
+ const char *loaded_path1 = "/some/where/but/not/scratch/dir";
+ const char *filename2 = "foo1";
+ gs_free char *loaded_path2 = g_strdup_printf ("%s/%s",
+ TEST_SCRATCH_DIR,
+ filename2);
+
+ _assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, NULL, FALSE, full_filename, NULL, NULL, NULL);
+ _assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, NULL, TRUE, full_filename, NULL, NULL, NULL);
+
+ _assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path0, FALSE, full_filename, uuid, loaded_path0, loaded_path0);
+ _assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path0, TRUE, full_filename, uuid, loaded_path0, loaded_path0);
+
+ _assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path1, FALSE, full_filename, uuid, loaded_path1, loaded_path1);
+ _assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path1, TRUE, full_filename, uuid, loaded_path1, loaded_path1);
+
+ _assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path2, FALSE, full_filename, uuid, loaded_path2, loaded_path2);
+ _assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path2, TRUE, full_filename, uuid, filename2, loaded_path2);
+
+ (void) unlink (full_filename);
+}
+
+/*****************************************************************************/
+
NMTST_DEFINE ();
int main (int argc, char **argv)
@@ -2593,6 +2696,8 @@ int main (int argc, char **argv)
g_test_add_func ("/keyfile/test_nm_keyfile_plugin_utils_escape_filename", test_nm_keyfile_plugin_utils_escape_filename);
+ g_test_add_func ("/keyfile/test_loaded_uuid", test_loaded_uuid);
+
return g_test_run ();
}