From: Arvin Schnell Date: Wed, 29 Jan 2014 10:59:44 +0000 (+0100) Subject: - allow to sync ALLOW_USERS and ALLOW_GROUPS to ACL of .snapshots directory (see... X-Git-Tag: v0.2.0^2~1 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=5e58ebe2d84eefe3883777be2a800e2a2a53890c;p=thirdparty%2Fsnapper.git - allow to sync ALLOW_USERS and ALLOW_GROUPS to ACL of .snapshots directory (see bnc#860116) --- diff --git a/LIBVERSION b/LIBVERSION index ccbccc3d..276cbf9e 100644 --- a/LIBVERSION +++ b/LIBVERSION @@ -1 +1 @@ -2.2.0 +2.3.0 diff --git a/VERSION b/VERSION index 699c6c6d..0ea3a944 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.8 +0.2.0 diff --git a/client/snapper.cc b/client/snapper.cc index e79b526e..886803ea 100644 --- a/client/snapper.cc +++ b/client/snapper.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) [2011-2013] Novell, Inc. + * Copyright (c) [2011-2014] Novell, Inc. * * All Rights Reserved. * @@ -488,12 +488,7 @@ command_set_config(DBus::Connection* conn, Snapper* snapper) if (no_dbus) { - ConfigInfo config_info = Snapper::getConfig(config_name); - - for (map::const_iterator it = raw.begin(); it != raw.end(); ++it) - config_info.setValue(it->first, it->second); - - config_info.save(); + snapper->setConfigInfo(raw); } else { @@ -1511,7 +1506,7 @@ main(int argc, char** argv) cmds.push_back(Cmd("create-config", command_create_config, help_create_config, true, false)); cmds.push_back(Cmd("delete-config", command_delete_config, help_delete_config, true, false)); cmds.push_back(Cmd("get-config", command_get_config, help_get_config, true, false)); - cmds.push_back(Cmd("set-config", command_set_config, help_set_config, true, false)); + cmds.push_back(Cmd("set-config", command_set_config, help_set_config, true, true)); cmds.push_back(Cmd("list", command_list, help_list, true, true)); cmds.push_back(Cmd("create", command_create, help_create, false, true)); cmds.push_back(Cmd("modify", command_modify, help_modify, false, true)); @@ -1647,6 +1642,21 @@ main(int argc, char** argv) cerr << _("Invalid configdata.") << endl; exit(EXIT_FAILURE); } + catch (const AclException& e) + { + cerr << _("ACL error.") << endl; + exit(EXIT_FAILURE); + } + catch (const InvalidUserException& e) + { + cerr << _("Invalid user.") << endl; + exit(EXIT_FAILURE); + } + catch (const InvalidGroupException& e) + { + cerr << _("Invalid group.") << endl; + exit(EXIT_FAILURE); + } } else { @@ -1687,6 +1697,12 @@ main(int argc, char** argv) cerr << _("Creating snapshot failed.") << endl; else if (name == "error.delete_snapshot_failed") cerr << _("Deleting snapshot failed.") << endl; + else if (name == "error.invalid_user") + cerr << _("Invalid user.") << endl; + else if (name == "error.invalid_group") + cerr << _("Invalid group.") << endl; + else if (name == "error.acl_error") + cerr << _("ACL error.") << endl; else cerr << _("Failure") << " (" << name << ")." << endl; exit(EXIT_FAILURE); diff --git a/data/default-config b/data/default-config index 71c80c8d..2c5f0c52 100644 --- a/data/default-config +++ b/data/default-config @@ -5,10 +5,16 @@ SUBVOLUME="/" # filesystem type FSTYPE="btrfs" + # users and groups allowed to work with config ALLOW_USERS="" ALLOW_GROUPS="" +# sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots +# directory +SYNC_ACLS="no" + + # start comparing pre- and post-snapshot in background after creating # post-snapshot BACKGROUND_COMPARISON="yes" diff --git a/doc/snapper-configs.xml.in b/doc/snapper-configs.xml.in index 8fb52135..896aa773 100644 --- a/doc/snapper-configs.xml.in +++ b/doc/snapper-configs.xml.in @@ -2,7 +2,7 @@ - 2013-07-09 + 2014-01-29 @@ -57,6 +57,8 @@ List of users allowed to operate with the config. The user-names must be separated by spaces. Spaces in usernames can be escaped with a "\". + Also see the PERMISSONS section in + snapper8. Default value is "" but "root" is always implicitly included. @@ -68,10 +70,25 @@ List of groups allowed to operate with the config. The group-names must be separated by spaces. Spaces in group-names can be escaped with a "\". + Also see the PERMISSONS section in + snapper8. Default value is "". + + + + Defines whether snapper will sync the users and groups from + ALLOW_USERS and ALLOW_GROUPS to the .snapshots + directory. + Also see the PERMISSONS section in + snapper8. + Default value is "no". + New in version 0.2.0. + + + diff --git a/doc/snapper.xml.in b/doc/snapper.xml.in index d13a006e..54fc1e39 100644 --- a/doc/snapper.xml.in +++ b/doc/snapper.xml.in @@ -2,7 +2,7 @@ - 2013-04-26 + 2014-01-29 @@ -490,6 +490,23 @@ user must also be able to read and access the .snapshots directory inside the subvolume. The .snapshots directory must be owned by root and must not be writable by anybody else. + Here are some methods how to achieve that: + + + Make the directory accessible for everyone: + chmod a+rx .snapshots + + + Make the directory accessible for a group the user belongs to, e.g.: + chown :users .snapshots + + + Make the directory accessible for the user using ACLs, e.g.: + setfacl -m u:tux:rx .snapshots + + + The last method can be performed by snapper, see the SYNC_ACLS setting in + snapper-configs5. diff --git a/package/snapper.changes b/package/snapper.changes index 21233141..f265d8b4 100644 --- a/package/snapper.changes +++ b/package/snapper.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Wed Jan 29 11:09:45 CET 2014 - aschnell@suse.de + +- allow to sync ALLOW_USERS and ALLOW_GROUPS to ACL of .snapshots + directory (see bnc#860116) +- 0.2.0 + ------------------------------------------------------------------- Fri Jan 24 10:01:35 CET 2014 - aschnell@suse.de diff --git a/server/Client.cc b/server/Client.cc index e88fefdf..651dc1f1 100644 --- a/server/Client.cc +++ b/server/Client.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) [2012-2013] Novell, Inc. + * Copyright (c) [2012-2014] Novell, Inc. * * All Rights Reserved. * @@ -1387,6 +1387,11 @@ Client::dispatch(DBus::Connection& conn, DBus::Message& msg) DBus::MessageError reply(msg, "error.invalid_userdata", DBUS_ERROR_FAILED); conn.send(reply); } + catch (const AclException& e) + { + DBus::MessageError reply(msg, "error.acl_error", DBUS_ERROR_FAILED); + conn.send(reply); + } catch (const IOErrorException& e) { DBus::MessageError reply(msg, "error.io_error", DBUS_ERROR_FAILED); @@ -1407,6 +1412,16 @@ Client::dispatch(DBus::Connection& conn, DBus::Message& msg) DBus::MessageError reply(msg, "error.umount_snapshot", DBUS_ERROR_FAILED); conn.send(reply); } + catch (const InvalidUserException& e) + { + DBus::MessageError reply(msg, "error.invalid_user", DBUS_ERROR_FAILED); + conn.send(reply); + } + catch (const InvalidGroupException& e) + { + DBus::MessageError reply(msg, "error.invalid_group", DBUS_ERROR_FAILED); + conn.send(reply); + } catch (...) { y2err("caught unknown exception"); diff --git a/server/MetaSnapper.cc b/server/MetaSnapper.cc index 501f9274..31686965 100644 --- a/server/MetaSnapper.cc +++ b/server/MetaSnapper.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) [2012-2013] Novell, Inc. + * Copyright (c) [2012-2014] Novell, Inc. * * All Rights Reserved. * @@ -22,11 +22,11 @@ #include #include -#include -#include #include #include +#include +#include #include "MetaSnapper.h" @@ -105,59 +105,6 @@ RefCounter::monotonic_time() } -bool -get_user_uid(const char* username, uid_t& uid) -{ - struct passwd pwd; - struct passwd* result; - - long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); - char buf[bufsize]; - - if (getpwnam_r(username, &pwd, buf, bufsize, &result) != 0 || result != &pwd) - { - y2war("couldn't find username '" << username << "'"); - return false; - } - - memset(pwd.pw_passwd, 0, strlen(pwd.pw_passwd)); - - uid = pwd.pw_uid; - - return true; -} - - -bool -get_group_uids(const char* groupname, vector& uids) -{ - struct group grp; - struct group* result; - - long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); - char buf[bufsize]; - - if (getgrnam_r(groupname, &grp, buf, bufsize, &result) != 0 || result != &grp) - { - y2war("couldn't find groupname '" << groupname << "'"); - return false; - } - - memset(grp.gr_passwd, 0, strlen(grp.gr_passwd)); - - uids.clear(); - - for (char** p = grp.gr_mem; *p != NULL; ++p) - { - uid_t uid; - if (get_user_uid(*p, uid)) - uids.push_back(uid); - } - - return true; -} - - MetaSnapper::MetaSnapper(ConfigInfo& config_info) : config_info(config_info), snapper(NULL) { @@ -177,9 +124,9 @@ MetaSnapper::setConfigInfo(const map& raw) for (map::const_iterator it = raw.begin(); it != raw.end(); ++it) config_info.setValue(it->first, it->second); - config_info.save(); + getSnapper()->setConfigInfo(raw); - if (raw.find("ALLOW_USERS") != raw.end() || raw.find("ALLOW_GROUPS") != raw.end()) + if (raw.find(KEY_ALLOW_USERS) != raw.end() || raw.find(KEY_ALLOW_GROUPS) != raw.end()) set_permissions(); } @@ -190,7 +137,7 @@ MetaSnapper::set_permissions() uids.clear(); vector users; - if (config_info.getValue("ALLOW_USERS", users)) + if (config_info.getValue(KEY_ALLOW_USERS, users)) { for (vector::const_iterator it = users.begin(); it != users.end(); ++it) { @@ -201,7 +148,7 @@ MetaSnapper::set_permissions() } vector groups; - if (config_info.getValue("ALLOW_GROUPS", groups)) + if (config_info.getValue(KEY_ALLOW_GROUPS, groups)) { for (vector::const_iterator it = groups.begin(); it != groups.end(); ++it) { diff --git a/snapper.spec.in b/snapper.spec.in index 87e0a273..a391b847 100644 --- a/snapper.spec.in +++ b/snapper.spec.in @@ -1,7 +1,7 @@ # # spec file for package snapper # -# Copyright (c) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany. +# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -24,6 +24,7 @@ Source: snapper-%{version}.tar.bz2 Prefix: /usr BuildRequires: boost-devel BuildRequires: gcc-c++ +BuildRequires: libacl-devel BuildRequires: libtool BuildRequires: libxml2-devel BuildRequires: pkg-config diff --git a/snapper/AppUtil.cc b/snapper/AppUtil.cc index 267230cf..24791c6c 100644 --- a/snapper/AppUtil.cc +++ b/snapper/AppUtil.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) [2004-2012] Novell, Inc. + * Copyright (c) [2004-2014] Novell, Inc. * * All Rights Reserved. * @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -281,6 +280,82 @@ namespace snapper } + bool + get_user_uid(const char* username, uid_t& uid) + { + struct passwd pwd; + struct passwd* result; + + long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + char buf[bufsize]; + + if (getpwnam_r(username, &pwd, buf, bufsize, &result) != 0 || result != &pwd) + { + y2war("couldn't find username '" << username << "'"); + return false; + } + + memset(pwd.pw_passwd, 0, strlen(pwd.pw_passwd)); + + uid = pwd.pw_uid; + + return true; + } + + + bool + get_group_uids(const char* groupname, vector& uids) + { + struct group grp; + struct group* result; + + long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); + char buf[bufsize]; + + if (getgrnam_r(groupname, &grp, buf, bufsize, &result) != 0 || result != &grp) + { + y2war("couldn't find groupname '" << groupname << "'"); + return false; + } + + memset(grp.gr_passwd, 0, strlen(grp.gr_passwd)); + + uids.clear(); + + for (char** p = grp.gr_mem; *p != NULL; ++p) + { + uid_t uid; + if (get_user_uid(*p, uid)) + uids.push_back(uid); + } + + return true; + } + + + bool + get_group_gid(const char* groupname, gid_t& gid) + { + struct group grp; + struct group* result; + + long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); + char buf[bufsize]; + + if (getgrnam_r(groupname, &grp, buf, bufsize, &result) != 0 || result != &grp) + { + y2war("couldn't find groupname '" << groupname << "'"); + return false; + } + + memset(grp.gr_passwd, 0, strlen(grp.gr_passwd)); + + gid = grp.gr_gid; + + return true; + } + + StopWatch::StopWatch() { gettimeofday(&start_tv, NULL); diff --git a/snapper/AppUtil.h b/snapper/AppUtil.h index 3304030a..5b9bfd8e 100644 --- a/snapper/AppUtil.h +++ b/snapper/AppUtil.h @@ -1,5 +1,5 @@ /* - * Copyright (c) [2004-2012] Novell, Inc. + * Copyright (c) [2004-2014] Novell, Inc. * * All Rights Reserved. * @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include #include @@ -84,6 +86,10 @@ namespace snapper string username(uid_t uid); + bool get_user_uid(const char* username, uid_t& uid); + bool get_group_gid(const char* groupname, gid_t& gid); + bool get_group_uids(const char* groupname, vector& uids); + class StopWatch { diff --git a/snapper/Exception.h b/snapper/Exception.h index 5deadd99..aeba0cec 100644 --- a/snapper/Exception.h +++ b/snapper/Exception.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 Novell, Inc. + * Copyright (c) [2011-2014] Novell, Inc. * * All Rights Reserved. * @@ -68,6 +68,12 @@ namespace snapper virtual const char* what() const throw() { return "IO error"; } }; + struct AclException : public IOErrorException + { + explicit AclException() throw() {} + virtual const char* what() const throw() { return "ACL error"; } + }; + struct ProgramNotInstalledException : public SnapperException { explicit ProgramNotInstalledException(const char* msg) throw() : msg(msg) {} @@ -81,6 +87,18 @@ namespace snapper virtual const char* what() const throw() { return "XAttributes error"; } }; + struct InvalidUserException : public SnapperException + { + explicit InvalidUserException() throw() {} + virtual const char* what() const throw() { return "invalid user"; } + }; + + struct InvalidGroupException : public SnapperException + { + explicit InvalidGroupException() throw() {} + virtual const char* what() const throw() { return "invalid group"; } + }; + } diff --git a/snapper/Makefile.am b/snapper/Makefile.am index a4a8c602..a6975afa 100644 --- a/snapper/Makefile.am +++ b/snapper/Makefile.am @@ -53,7 +53,7 @@ endif libsnapper_la_LDFLAGS = -version-info @LIBVERSION_INFO@ -libsnapper_la_LIBADD = -lboost_thread-mt -lboost_system-mt -lxml2 -lz -lm +libsnapper_la_LIBADD = -lboost_thread-mt -lboost_system-mt -lxml2 -lacl -lz -lm pkgincludedir = $(includedir)/snapper diff --git a/snapper/Snapper.cc b/snapper/Snapper.cc index 400ced6f..08fa70b9 100644 --- a/snapper/Snapper.cc +++ b/snapper/Snapper.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) [2011-2013] Novell, Inc. + * Copyright (c) [2011-2014] Novell, Inc. * * All Rights Reserved. * @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include #include #include "snapper/Snapper.h" @@ -50,7 +53,7 @@ namespace snapper ConfigInfo::ConfigInfo(const string& config_name) : SysconfigFile(CONFIGSDIR "/" + config_name), config_name(config_name), subvolume("/") { - if (!getValue("SUBVOLUME", subvolume)) + if (!getValue(KEY_SUBVOLUME, subvolume)) throw InvalidConfigException(); } @@ -58,7 +61,7 @@ namespace snapper void ConfigInfo::checkKey(const string& key) const { - if (key == "SUBVOLUME" || key == "FSTYPE") + if (key == KEY_SUBVOLUME || key == KEY_FSTYPE) throw InvalidConfigdataException(); try @@ -89,7 +92,7 @@ namespace snapper } string fstype = "btrfs"; - config_info->getValue("FSTYPE", fstype); + config_info->getValue(KEY_FSTYPE, fstype); filesystem = Filesystem::create(fstype, config_info->getSubvolume()); y2mil("subvolume:" << config_info->getSubvolume() << " filesystem:" << @@ -359,8 +362,8 @@ namespace snapper try { SysconfigFile config(CONFIGSDIR "/" + config_name); - config.setValue("SUBVOLUME", subvolume); - config.setValue("FSTYPE", filesystem->fstype()); + config.setValue(KEY_SUBVOLUME, subvolume); + config.setValue(KEY_FSTYPE, filesystem->fstype()); } catch (const FileNotFoundException& e) { @@ -444,6 +447,176 @@ namespace snapper } + void + Snapper::setConfigInfo(const map& raw) + { + for (map::const_iterator it = raw.begin(); it != raw.end(); ++it) + config_info->setValue(it->first, it->second); + + config_info->save(); + + if (raw.find(KEY_ALLOW_USERS) != raw.end() || raw.find(KEY_ALLOW_GROUPS) != raw.end() || + raw.find(KEY_SYNC_ACLS) != raw.end()) + { + bool sync_acl; + if (config_info->getValue(KEY_SYNC_ACLS, sync_acl) && sync_acl == true) + syncAcls(); + } + } + + + void + Snapper::syncAcls() const + { + vector uids; + vector users; + if (config_info->getValue(KEY_ALLOW_USERS, users)) + { + for (vector::const_iterator it = users.begin(); it != users.end(); ++it) + { + uid_t uid; + if (!get_user_uid(it->c_str(), uid)) + throw InvalidUserException(); + uids.push_back(uid); + } + } + + vector gids; + vector groups; + if (config_info->getValue(KEY_ALLOW_GROUPS, groups)) + { + for (vector::const_iterator it = groups.begin(); it != groups.end(); ++it) + { + gid_t gid; + if (!get_group_gid(it->c_str(), gid)) + throw InvalidGroupException(); + gids.push_back(gid); + } + } + + syncAcls(uids, gids); + } + + + static void + set_acl_permissions(acl_entry_t entry) + { + acl_permset_t permset; + if (acl_get_permset(entry, &permset) != 0) + throw AclException(); + + if (acl_add_perm(permset, ACL_READ) != 0 || acl_delete_perm(permset, ACL_WRITE) != 0 || + acl_add_perm(permset, ACL_EXECUTE) != 0) + throw AclException(); + }; + + + static void + add_acl_permissions(acl_t* acl, acl_tag_t tag, const void* qualifier) + { + acl_entry_t entry; + if (acl_create_entry(acl, &entry) != 0) + throw AclException(); + + if (acl_set_tag_type(entry, tag) != 0) + throw AclException(); + + if (acl_set_qualifier(entry, qualifier) != 0) + throw AclException(); + + set_acl_permissions(entry); + }; + + + void + Snapper::syncAcls(const vector& uids, const vector& gids) const + { + SDir infos_dir = openInfosDir(); + + acl_t acl = acl_get_fd(infos_dir.fd()); + if (!acl) + throw AclException(); + + set remaining_uids = set(uids.begin(), uids.end()); + set remaining_gids = set(gids.begin(), gids.end()); + + acl_entry_t entry; + if (acl_get_entry(acl, ACL_FIRST_ENTRY, &entry) == 1) + { + do { + + acl_tag_t tag; + if (acl_get_tag_type(entry, &tag) != 0) + throw AclException(); + + switch (tag) + { + case ACL_USER: { + + uid_t* uid = (uid_t*) acl_get_qualifier(entry); + if (!uid) + throw AclException(); + + if (contains(remaining_uids, *uid)) + { + remaining_uids.erase(*uid); + + set_acl_permissions(entry); + } + else + { + if (acl_delete_entry(acl, entry) != 0) + throw AclException(); + } + + } break; + + case ACL_GROUP: { + + gid_t* gid = (gid_t*) acl_get_qualifier(entry); + if (!gid) + throw AclException(); + + if (contains(remaining_gids, *gid)) + { + remaining_gids.erase(*gid); + + set_acl_permissions(entry); + } + else + { + if (acl_delete_entry(acl, entry) != 0) + throw AclException(); + } + + } break; + + } + + } while (acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) == 1); + } + + for (set::const_iterator it = remaining_uids.begin(); it != remaining_uids.end(); ++it) + { + add_acl_permissions(&acl, ACL_USER, &(*it)); + } + + for (set::const_iterator it = remaining_gids.begin(); it != remaining_gids.end(); ++it) + { + add_acl_permissions(&acl, ACL_GROUP, &(*it)); + } + + if (acl_calc_mask(&acl) != 0) + throw AclException(); + + if (acl_set_fd(infos_dir.fd(), acl) != 0) + throw AclException(); + + if (acl_free(acl) != 0) + throw AclException(); + } + + static bool is_subpath(const string& a, const string& b) { diff --git a/snapper/Snapper.h b/snapper/Snapper.h index 1f30b74a..730ac8b6 100644 --- a/snapper/Snapper.h +++ b/snapper/Snapper.h @@ -1,5 +1,5 @@ /* - * Copyright (c) [2011-2013] Novell, Inc. + * Copyright (c) [2011-2014] Novell, Inc. * * All Rights Reserved. * @@ -156,6 +156,10 @@ namespace snapper const Filesystem* getFilesystem() const { return filesystem; } + void setConfigInfo(const map& raw); + + void syncAcls() const; + private: void filter1(list& tmp, time_t min_age); @@ -163,6 +167,8 @@ namespace snapper void loadIgnorePatterns(); + void syncAcls(const vector& uids, const vector& gids) const; + ConfigInfo* config_info; Filesystem* filesystem; diff --git a/snapper/SnapperDefines.h b/snapper/SnapperDefines.h index 240a6bd4..c26ad10f 100644 --- a/snapper/SnapperDefines.h +++ b/snapper/SnapperDefines.h @@ -1,5 +1,5 @@ /* - * Copyright (c) [2004-2013] Novell, Inc. + * Copyright (c) [2004-2014] Novell, Inc. * * All Rights Reserved. * @@ -24,6 +24,8 @@ #define SNAPPER_SNAPPER_DEFINES_H +// path definitions + #define SYSCONFIGFILE CONFDIR "/snapper" #define CONFIGSDIR "/etc/snapper/configs" @@ -32,4 +34,14 @@ #define FILTERSDIR "/etc/snapper/filters" +// keys from the config files + +#define KEY_SUBVOLUME "SUBVOLUME" +#define KEY_FSTYPE "FSTYPE" + +#define KEY_ALLOW_USERS "ALLOW_USERS" +#define KEY_ALLOW_GROUPS "ALLOW_GROUPS" +#define KEY_SYNC_ACLS "SYNC_ACLS" + + #endif