/*
- * Copyright (c) [2011-2013] Novell, Inc.
+ * Copyright (c) [2011-2014] Novell, Inc.
*
* All Rights Reserved.
*
if (no_dbus)
{
- ConfigInfo config_info = Snapper::getConfig(config_name);
-
- for (map<string, string>::const_iterator it = raw.begin(); it != raw.end(); ++it)
- config_info.setValue(it->first, it->second);
-
- config_info.save();
+ snapper->setConfigInfo(raw);
}
else
{
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));
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
{
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);
# 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"
<refentry id='snapper-configs5'>
<refentryinfo>
- <date>2013-07-09</date>
+ <date>2014-01-29</date>
</refentryinfo>
<refmeta>
<para>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
"\".</para>
+ <para>Also see the PERMISSONS section in
+ <citerefentry><refentrytitle>snapper</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<para>Default value is "" but "root" is always
implicitly included.</para>
</listitem>
<para>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
"\".</para>
+ <para>Also see the PERMISSONS section in
+ <citerefentry><refentrytitle>snapper</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<para>Default value is "".</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>SYNC_ACLS=<replaceable>boolean</replaceable></option></term>
+ <listitem>
+ <para>Defines whether snapper will sync the users and groups from
+ ALLOW_USERS and ALLOW_GROUPS to the <filename>.snapshots</filename>
+ directory.</para>
+ <para>Also see the PERMISSONS section in
+ <citerefentry><refentrytitle>snapper</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+ <para>Default value is "no".</para>
+ <para>New in version 0.2.0.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>BACKGROUND_COMPARISON=<replaceable>boolean</replaceable></option></term>
<listitem>
<refentry id='snapper8'>
<refentryinfo>
- <date>2013-04-26</date>
+ <date>2014-01-29</date>
</refentryinfo>
<refmeta>
user must also be able to read and access the <filename>.snapshots</filename>
directory inside the subvolume. The <filename>.snapshots</filename> directory
must be owned by root and must not be writable by anybody else.</para>
+ <para>Here are some methods how to achieve that:</para>
+ <itemizedlist>
+ <listitem>
+ <para>Make the directory accessible for everyone:</para>
+ <para><command>chmod a+rx .snapshots</command></para>
+ </listitem>
+ <listitem>
+ <para>Make the directory accessible for a group the user belongs to, e.g.:</para>
+ <para><command>chown :users .snapshots</command></para>
+ </listitem>
+ <listitem>
+ <para>Make the directory accessible for the user using ACLs, e.g.:</para>
+ <para><command>setfacl -m u:tux:rx .snapshots</command></para>
+ </listitem>
+ </itemizedlist>
+ <para>The last method can be performed by snapper, see the SYNC_ACLS setting in
+ <citerefentry><refentrytitle>snapper-configs</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
</refsect1>
<refsect1 id='files'>
+-------------------------------------------------------------------
+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
/*
- * Copyright (c) [2012-2013] Novell, Inc.
+ * Copyright (c) [2012-2014] Novell, Inc.
*
* All Rights Reserved.
*
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);
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");
/*
- * Copyright (c) [2012-2013] Novell, Inc.
+ * Copyright (c) [2012-2014] Novell, Inc.
*
* All Rights Reserved.
*
#include <string.h>
#include <sys/types.h>
-#include <pwd.h>
-#include <grp.h>
#include <boost/algorithm/string.hpp>
#include <snapper/Log.h>
+#include <snapper/AppUtil.h>
+#include <snapper/SnapperDefines.h>
#include "MetaSnapper.h"
}
-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<uid_t>& 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)
{
for (map<string, string>::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();
}
uids.clear();
vector<string> users;
- if (config_info.getValue("ALLOW_USERS", users))
+ if (config_info.getValue(KEY_ALLOW_USERS, users))
{
for (vector<string>::const_iterator it = users.begin(); it != users.end(); ++it)
{
}
vector<string> groups;
- if (config_info.getValue("ALLOW_GROUPS", groups))
+ if (config_info.getValue(KEY_ALLOW_GROUPS, groups))
{
for (vector<string>::const_iterator it = groups.begin(); it != groups.end(); ++it)
{
#
# 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
Prefix: /usr
BuildRequires: boost-devel
BuildRequires: gcc-c++
+BuildRequires: libacl-devel
BuildRequires: libtool
BuildRequires: libxml2-devel
BuildRequires: pkg-config
/*
- * Copyright (c) [2004-2012] Novell, Inc.
+ * Copyright (c) [2004-2014] Novell, Inc.
*
* All Rights Reserved.
*
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/sendfile.h>
-#include <pwd.h>
#include <dirent.h>
#include <mntent.h>
#include <boost/algorithm/string.hpp>
}
+ 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<uid_t>& 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);
/*
- * Copyright (c) [2004-2012] Novell, Inc.
+ * Copyright (c) [2004-2014] Novell, Inc.
*
* All Rights Reserved.
*
#include <sys/time.h>
#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
#include <sstream>
#include <locale>
#include <string>
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<uid_t>& uids);
+
class StopWatch
{
/*
- * Copyright (c) 2011 Novell, Inc.
+ * Copyright (c) [2011-2014] Novell, Inc.
*
* All Rights Reserved.
*
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) {}
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"; }
+ };
+
}
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
/*
- * Copyright (c) [2011-2013] Novell, Inc.
+ * Copyright (c) [2011-2014] Novell, Inc.
*
* All Rights Reserved.
*
#include <glob.h>
#include <string.h>
#include <mntent.h>
+#include <sys/acl.h>
+#include <acl/libacl.h>
+#include <set>
#include <boost/algorithm/string.hpp>
#include "snapper/Snapper.h"
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();
}
void
ConfigInfo::checkKey(const string& key) const
{
- if (key == "SUBVOLUME" || key == "FSTYPE")
+ if (key == KEY_SUBVOLUME || key == KEY_FSTYPE)
throw InvalidConfigdataException();
try
}
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:" <<
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)
{
}
+ void
+ Snapper::setConfigInfo(const map<string, string>& raw)
+ {
+ for (map<string, string>::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<uid_t> uids;
+ vector<string> users;
+ if (config_info->getValue(KEY_ALLOW_USERS, users))
+ {
+ for (vector<string>::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<gid_t> gids;
+ vector<string> groups;
+ if (config_info->getValue(KEY_ALLOW_GROUPS, groups))
+ {
+ for (vector<string>::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<uid_t>& uids, const vector<gid_t>& gids) const
+ {
+ SDir infos_dir = openInfosDir();
+
+ acl_t acl = acl_get_fd(infos_dir.fd());
+ if (!acl)
+ throw AclException();
+
+ set<uid_t> remaining_uids = set<uid_t>(uids.begin(), uids.end());
+ set<gid_t> remaining_gids = set<gid_t>(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<uid_t>::const_iterator it = remaining_uids.begin(); it != remaining_uids.end(); ++it)
+ {
+ add_acl_permissions(&acl, ACL_USER, &(*it));
+ }
+
+ for (set<gid_t>::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)
{
/*
- * Copyright (c) [2011-2013] Novell, Inc.
+ * Copyright (c) [2011-2014] Novell, Inc.
*
* All Rights Reserved.
*
const Filesystem* getFilesystem() const { return filesystem; }
+ void setConfigInfo(const map<string, string>& raw);
+
+ void syncAcls() const;
+
private:
void filter1(list<Snapshots::iterator>& tmp, time_t min_age);
void loadIgnorePatterns();
+ void syncAcls(const vector<uid_t>& uids, const vector<gid_t>& gids) const;
+
ConfigInfo* config_info;
Filesystem* filesystem;
/*
- * Copyright (c) [2004-2013] Novell, Inc.
+ * Copyright (c) [2004-2014] Novell, Inc.
*
* All Rights Reserved.
*
#define SNAPPER_SNAPPER_DEFINES_H
+// path definitions
+
#define SYSCONFIGFILE CONFDIR "/snapper"
#define CONFIGSDIR "/etc/snapper/configs"
#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