From 1168041d57de82d2d13e166832ca5054d1d0245b Mon Sep 17 00:00:00 2001 From: James Antill Date: Thu, 20 Nov 2014 09:40:35 -0500 Subject: [PATCH] compose: Add --check-passwd/group options, to fail if uids/gids change --- src/rpmostree-compose-builtin-tree.c | 391 +++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) diff --git a/src/rpmostree-compose-builtin-tree.c b/src/rpmostree-compose-builtin-tree.c index c82355b78a..31124ca4b0 100644 --- a/src/rpmostree-compose-builtin-tree.c +++ b/src/rpmostree-compose-builtin-tree.c @@ -52,6 +52,8 @@ static char **opt_metadata_strings; static char *opt_repo; static char **opt_override_pkg_repos; static gboolean opt_print_only; +static char *opt_check_passwd = NULL; +static char *opt_check_groups = NULL; static GOptionEntry option_entries[] = { { "add-metadata-string", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_metadata_strings, "Append given key and value (in string format) to metadata", "KEY=VALUE" }, @@ -63,6 +65,8 @@ static GOptionEntry option_entries[] = { { "add-override-pkg-repo", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_override_pkg_repos, "Include an additional package repository from DIRECTORY", "DIRECTORY" }, { "proxy", 0, 0, G_OPTION_ARG_STRING, &opt_proxy, "HTTP proxy", "PROXY" }, { "print-only", 0, 0, G_OPTION_ARG_NONE, &opt_print_only, "Just expand any includes and print treefile", NULL }, + { "check-passwd", 0, 0, G_OPTION_ARG_STRING, &opt_check_passwd, "Old passwd", "PASSWD" }, + { "check-groups", 0, 0, G_OPTION_ARG_STRING, &opt_check_groups, "Old groups", "GROP" }, { NULL } }; @@ -796,6 +800,387 @@ bind_mount_readonly (const char *path, GError **error) return ret; } +static int +compare_strings (gconstpointer a, gconstpointer b) +{ + const char **sa = (const char **)a; + const char **sb = (const char **)b; + + return strcmp (*sa, *sb); +} + +static char * +load_file_direct_or_rev (OstreeRepo *repo, + const char *direct_or_rev, + const char *path, + GCancellable *cancellable, + GError **error) +{ + gs_unref_object GFile *root = NULL; + gs_unref_object GFile *fpathd = g_file_new_for_path (direct_or_rev); + gs_unref_object GFile *fpathc = NULL; + GError *tmp_error = NULL; + char *ret = NULL; + + ret = gs_file_load_contents_utf8 (fpathd, cancellable, &tmp_error); + if (ret) + goto out; + + if (!path) + { + g_propagate_error (error, tmp_error); + goto out; + } + g_clear_error (&tmp_error); + + if (!ostree_repo_read_commit (repo, direct_or_rev, &root, NULL, NULL, error)) + goto out; + + fpathc = g_file_resolve_relative_path (root, path); + ret = gs_file_load_contents_utf8 (fpathc, cancellable, error); + + out: + return ret; +} + +/* /usr/lib/passwd Format is: + :::::: + ...we just need to make sure the name and udi/gid match for the same logins, + and that none are missing. don't care about GECOS/dir/shell. +*/ +static gboolean +parse_passwd_line (const char *ftype, + char *line, + char **ret_name, + char **ret_data, + GError **error) +{ + int ret = FALSE; + char *name = NULL; + char *data = NULL; + char *end = NULL; + + data = name = line; + data += strcspn (data, ":"); + if (!*data) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "%s passwd entry malformed just name: %s", ftype, line); + goto out; + } + *data++ = 0; + + data += strcspn (data, ":"); + if (!*data) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "%s passwd entry malformed just name+pass: %s", ftype, line); + goto out; + } + *data++ = 0; + + end = data; + end += strcspn (end, ":"); + end += strspn (end, ":"); + end += strcspn (end, ":"); + *end = 0; + + *ret_name = name; + *ret_data = data; + + ret = TRUE; + out: + return ret; +} + +static gboolean +rpmostree_check_passwd (OstreeRepo *repo, + GFile *yumroot, + JsonObject *treedata, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_object GFile *new_path = g_file_resolve_relative_path (yumroot, "usr/lib/passwd"); + gs_free char *old_contents = NULL; + gs_free char *new_contents = NULL; + gs_strfreev char **old_lines = NULL; + gs_strfreev char **new_lines = NULL; + char **oiter = NULL; + char **niter = NULL; + char *nname = NULL; + char *ndata = NULL; + char *oname = NULL; + char *odata = NULL; + + old_contents = load_file_direct_or_rev (repo, + opt_check_passwd, "usr/lib/passwd", + cancellable, error); + if (!old_contents) + goto out; + + new_contents = gs_file_load_contents_utf8 (new_path, cancellable, error); + if (!new_contents) + goto out; + + old_lines = g_strsplit (old_contents, "\n", -1); + qsort (old_lines, g_strv_length (old_lines),sizeof (char *), compare_strings); + + new_lines = g_strsplit (new_contents, "\n", -1); + qsort (new_lines, g_strv_length (new_lines),sizeof (char *), compare_strings); + + oiter = old_lines; + niter = new_lines; + while (oiter && *oiter && niter && *niter) + { + int cmp = 0; + + if (!**oiter) + { + ++oiter; + oname = NULL; + continue; + } + if (!**niter) + { + ++niter; + nname = NULL; + continue; + } + + if (!oname) + { + if (!parse_passwd_line ("OLD", *oiter, &oname, &odata, error)) + goto out; + } + + if (!nname) + { + if (!parse_passwd_line ("NEW", *niter, &nname, &ndata, error)) + goto out; + } + + cmp = g_strcmp0 (oname, nname); + + if (cmp == 0) + { + cmp = g_strcmp0 (odata, ndata); + if (cmp) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "passwd entry changed: %s\n o=%s\n n=%s", + oname, odata, ndata); + goto out; + } + } + + if (cmp == 0) + { + ++niter; + nname = NULL; + ++oiter; + oname = NULL; + continue; + } + + if (cmp < 0) // Missing value from new passwd + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "User missing from new passwd: %s", *oiter); + goto out; + } + + g_print ("New passwd entry: %s\n", *niter); + ++niter; + nname = NULL; + } + + if (oiter && *oiter) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "User missing from new passwd: %s", *oiter); + goto out; + } + + while (niter && *niter) + { + g_print ("New passwd entry: %s\n", *niter); + ++niter; + } + + ret = TRUE; + out: + return ret; +} + +/* /usr/lib/group Format is: + ::: + ...we just need to make sure the name and gid match for the same groups, + and that none are missing. Don't care about users. + */ +static gboolean +parse_groups_line (const char *ftype, + char *line, + char **ret_name, + char **ret_data, + GError **error) +{ + int ret = FALSE; + char *name = NULL; + char *data = NULL; + char *end = NULL; + + data = name = line; + data += strcspn (data, ":"); + if (!*data) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "%s group entry malformed just name: %s", ftype, line); + goto out; + } + *data++ = 0; + + data += strcspn (data, ":"); + if (!*data) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "%s group entry malformed just name+pass: %s", ftype, line); + goto out; + } + *data++ = 0; + + end = data; + end += strcspn (end, ":"); + *end = 0; + + *ret_name = name; + *ret_data = data; + + ret = TRUE; + out: + return ret; +} + +static gboolean +rpmostree_check_groups (OstreeRepo *repo, + GFile *yumroot, + JsonObject *treedata, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_object GFile *new_path = g_file_resolve_relative_path (yumroot, "usr/lib/group"); + gs_free char *old_contents = NULL; + gs_free char *new_contents = NULL; + gs_strfreev char **old_lines = NULL; + gs_strfreev char **new_lines = NULL; + char **oiter = NULL; + char **niter = NULL; + char *nname = NULL; + char *ndata = NULL; + char *oname = NULL; + char *odata = NULL; + + old_contents = load_file_direct_or_rev (repo, + opt_check_groups, "usr/lib/group", + cancellable, error); + if (!old_contents) + goto out; + + new_contents = gs_file_load_contents_utf8 (new_path, cancellable, error); + if (!new_contents) + goto out; + + old_lines = g_strsplit (old_contents, "\n", -1); + qsort (old_lines, g_strv_length (old_lines),sizeof (char *), compare_strings); + + new_lines = g_strsplit (new_contents, "\n", -1); + qsort (new_lines, g_strv_length (new_lines),sizeof (char *), compare_strings); + + oiter = old_lines; + niter = new_lines; + while (oiter && *oiter && niter && *niter) + { + int cmp = 0; + + if (!**oiter) + { + ++oiter; + oname = NULL; + continue; + } + if (!**niter) + { + ++niter; + nname = NULL; + continue; + } + + if (!oname) + { + if (!parse_groups_line ("OLD", *oiter, &oname, &odata, error)) + goto out; + } + + if (!nname) + { + if (!parse_groups_line ("NEW", *niter, &nname, &ndata, error)) + goto out; + } + + cmp = g_strcmp0 (oname, nname); + + if (cmp == 0) + { + cmp = g_strcmp0 (odata, ndata); + if (cmp) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "group entry changed: %s\n o=%s\n n=%s", + oname, odata, ndata); + goto out; + } + } + + if (cmp == 0) + { + ++niter; + nname = NULL; + ++oiter; + oname = NULL; + continue; + } + + if (cmp < 0) // Missing value from new group + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Group missing from new group: %s", *oiter); + goto out; + } + + g_print ("New group entry: %s\n", *niter); + ++niter; + nname = NULL; + } + + if (oiter && *oiter) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "group entry missing: %s", *oiter); + goto out; + } + + while (niter && *niter) + { + g_print ("New group entry: %s\n", *niter); + ++niter; + } + + ret = TRUE; + out: + return ret; +} + gboolean rpmostree_compose_builtin_tree (int argc, char **argv, @@ -1046,6 +1431,12 @@ rpmostree_compose_builtin_tree (int argc, if (!rpmostree_prepare_rootfs_for_commit (yumroot, treefile, cancellable, error)) goto out; + if (!rpmostree_check_passwd (repo, yumroot, treefile, cancellable, error)) + goto out; + + if (!rpmostree_check_groups (repo, yumroot, treefile, cancellable, error)) + goto out; + { const char *gpgkey; if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "gpg_key", &gpgkey, error))