*/
#include "includes.h"
+#include "system/passwd.h"
#include "auth/auth.h"
#include "vfs_posix.h"
#include "librpc/gen_ndr/xattr.h"
#include "libcli/security/security.h"
-
+#include "param/param.h"
+#include "../lib/util/unix_privs.h"
+#include "lib/util/samba_module.h"
/* the list of currently registered ACL backends */
static struct pvfs_acl_backend {
backends = talloc_realloc(talloc_autofree_context(), backends, struct pvfs_acl_backend, num_backends+1);
NT_STATUS_HAVE_NO_MEMORY(backends);
- new_ops = talloc_memdup(backends, ops, sizeof(*ops));
+ new_ops = (struct pvfs_acl_ops *)talloc_memdup(backends, ops, sizeof(*ops));
new_ops->name = talloc_strdup(new_ops, ops->name);
backends[num_backends].ops = new_ops;
return NULL;
}
+NTSTATUS pvfs_acl_init(void)
+{
+ static bool initialized = false;
+#define _MODULE_PROTO(init) extern NTSTATUS init(void);
+ STATIC_pvfs_acl_MODULES_PROTO;
+ samba_init_module_fn static_init[] = { STATIC_pvfs_acl_MODULES };
+ samba_init_module_fn *shared_init;
+
+ if (initialized) return NT_STATUS_OK;
+ initialized = true;
+
+ shared_init = samba_modules_load(NULL, "pvfs_acl");
+
+ samba_init_module_fns_run(static_init);
+ samba_init_module_fns_run(shared_init);
+
+ talloc_free(shared_init);
+
+ return NT_STATUS_OK;
+}
+
/*
map a single access_mask from generic to specific bits for files/dirs
NTSTATUS status;
struct security_ace ace;
mode_t mode;
+ struct id_map *ids;
+ struct composite_context *ctx;
*psd = security_descriptor_initialise(req);
if (*psd == NULL) {
}
sd = *psd;
- status = sidmap_uid_to_sid(pvfs->sidmap, sd, name->st.st_uid, &sd->owner_sid);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
- status = sidmap_gid_to_sid(pvfs->sidmap, sd, name->st.st_gid, &sd->group_sid);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
+ ids = talloc_zero_array(sd, struct id_map, 2);
+ NT_STATUS_HAVE_NO_MEMORY(ids);
+
+ ids[0].xid.id = name->st.st_uid;
+ ids[0].xid.type = ID_TYPE_UID;
+ ids[0].sid = NULL;
+
+ ids[1].xid.id = name->st.st_gid;
+ ids[1].xid.type = ID_TYPE_GID;
+ ids[1].sid = NULL;
+
+ ctx = wbc_xids_to_sids_send(pvfs->wbc_ctx, ids, 2, ids);
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+
+ status = wbc_xids_to_sids_recv(ctx, &ids);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ sd->owner_sid = talloc_steal(sd, ids[0].sid);
+ sd->group_sid = talloc_steal(sd, ids[1].sid);
+ talloc_free(ids);
sd->type |= SEC_DESC_DACL_PRESENT;
mode = name->st.st_mode;
gid_t old_gid = -1;
uid_t new_uid = -1;
gid_t new_gid = -1;
+ struct id_map *ids;
+ struct composite_context *ctx;
if (pvfs->acl_ops != NULL) {
status = pvfs->acl_ops->acl_load(pvfs, name, fd, req, &sd);
return status;
}
+ ids = talloc(req, struct id_map);
+ NT_STATUS_HAVE_NO_MEMORY(ids);
+ ZERO_STRUCT(ids->xid);
+ ids->sid = NULL;
+ ids->status = ID_UNKNOWN;
+
new_sd = info->set_secdesc.in.sd;
orig_sd = *sd;
return NT_STATUS_ACCESS_DENIED;
}
if (!dom_sid_equal(sd->owner_sid, new_sd->owner_sid)) {
- status = sidmap_sid_to_unixuid(pvfs->sidmap, new_sd->owner_sid, &new_uid);
+ ids->sid = new_sd->owner_sid;
+ ctx = wbc_sids_to_xids_send(pvfs->wbc_ctx, ids, 1, ids);
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+ status = wbc_sids_to_xids_recv(ctx, &ids);
NT_STATUS_NOT_OK_RETURN(status);
+
+ if (ids->xid.type == ID_TYPE_BOTH ||
+ ids->xid.type == ID_TYPE_UID) {
+ new_uid = ids->xid.id;
+ }
}
sd->owner_sid = new_sd->owner_sid;
}
return NT_STATUS_ACCESS_DENIED;
}
if (!dom_sid_equal(sd->group_sid, new_sd->group_sid)) {
- status = sidmap_sid_to_unixgid(pvfs->sidmap, new_sd->group_sid, &new_gid);
+ ids->sid = new_sd->group_sid;
+ ctx = wbc_sids_to_xids_send(pvfs->wbc_ctx, ids, 1, ids);
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+ status = wbc_sids_to_xids_recv(ctx, &ids);
NT_STATUS_NOT_OK_RETURN(status);
+
+ if (ids->xid.type == ID_TYPE_BOTH ||
+ ids->xid.type == ID_TYPE_GID) {
+ new_gid = ids->xid.id;
+ }
+
}
sd->group_sid = new_sd->group_sid;
}
} else {
ret = fchown(fd, new_uid, new_gid);
}
+ if (errno == EPERM) {
+ if (uwrap_enabled()) {
+ ret = 0;
+ } else {
+ /* try again as root if we have SEC_PRIV_RESTORE or
+ SEC_PRIV_TAKE_OWNERSHIP */
+ if (security_token_has_privilege(req->session_info->security_token,
+ SEC_PRIV_RESTORE) ||
+ security_token_has_privilege(req->session_info->security_token,
+ SEC_PRIV_TAKE_OWNERSHIP)) {
+ void *privs;
+ privs = root_privileges();
+ if (fd == -1) {
+ ret = chown(name->full_name, new_uid, new_gid);
+ } else {
+ ret = fchown(fd, new_uid, new_gid);
+ }
+ talloc_free(privs);
+ }
+ }
+ }
if (ret == -1) {
return pvfs_map_errno(pvfs, errno);
}
return false;
}
+/*
+ see if we are a member of the appropriate unix group
+ */
+static bool pvfs_group_member(struct pvfs_state *pvfs, gid_t gid)
+{
+ int i, ngroups;
+ gid_t *groups;
+ if (getegid() == gid) {
+ return true;
+ }
+ ngroups = getgroups(0, NULL);
+ if (ngroups == 0) {
+ return false;
+ }
+ groups = talloc_array(pvfs, gid_t, ngroups);
+ if (groups == NULL) {
+ return false;
+ }
+ if (getgroups(ngroups, groups) != ngroups) {
+ talloc_free(groups);
+ return false;
+ }
+ for (i=0; i<ngroups; i++) {
+ if (groups[i] == gid) break;
+ }
+ talloc_free(groups);
+ return i < ngroups;
+}
+
/*
default access check function based on unix permissions
doing this saves on building a full security descriptor
for the common case of access check on files with no
specific NT ACL
+
+ If name is NULL then treat as a new file creation
*/
-NTSTATUS pvfs_access_check_unix(struct pvfs_state *pvfs,
- struct ntvfs_request *req,
- struct pvfs_filename *name,
- uint32_t *access_mask)
+static NTSTATUS pvfs_access_check_unix(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ uint32_t *access_mask)
{
uid_t uid = geteuid();
uint32_t max_bits = SEC_RIGHTS_FILE_READ | SEC_FILE_ALL;
+ struct security_token *token = req->session_info->security_token;
if (pvfs_read_only(pvfs, *access_mask)) {
return NT_STATUS_ACCESS_DENIED;
}
- /* owner and root get extra permissions */
- if (uid == 0) {
- max_bits |= SEC_STD_ALL | SEC_FLAG_SYSTEM_SECURITY;
- } else if (uid == name->st.st_uid) {
+ if (name == NULL || uid == name->st.st_uid) {
max_bits |= SEC_STD_ALL;
+ } else if (security_token_has_privilege(token, SEC_PRIV_RESTORE)) {
+ max_bits |= SEC_STD_DELETE;
}
- if (*access_mask == SEC_FLAG_MAXIMUM_ALLOWED) {
- *access_mask = max_bits;
- return NT_STATUS_OK;
+ if (name == NULL ||
+ (name->st.st_mode & S_IWOTH) ||
+ ((name->st.st_mode & S_IWGRP) &&
+ pvfs_group_member(pvfs, name->st.st_gid))) {
+ max_bits |= SEC_STD_ALL;
+ }
+
+ if (uwrap_enabled()) {
+ /* when running with the uid wrapper, files will be created
+ owned by the ruid, but we may have a different simulated
+ euid. We need to force the permission bits as though the
+ files owner matches the euid */
+ max_bits |= SEC_STD_ALL;
}
- if (uid != 0 && (*access_mask & SEC_FLAG_SYSTEM_SECURITY)) {
- return NT_STATUS_PRIVILEGE_NOT_HELD;
+ if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED) {
+ *access_mask |= max_bits;
+ *access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED;
+ }
+
+ if ((*access_mask & SEC_FLAG_SYSTEM_SECURITY) &&
+ security_token_has_privilege(token, SEC_PRIV_SECURITY)) {
+ max_bits |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+
+ if (((*access_mask & ~max_bits) & SEC_RIGHTS_PRIV_RESTORE) &&
+ security_token_has_privilege(token, SEC_PRIV_RESTORE)) {
+ max_bits |= ~(SEC_RIGHTS_PRIV_RESTORE);
+ }
+ if (((*access_mask & ~max_bits) & SEC_RIGHTS_PRIV_BACKUP) &&
+ security_token_has_privilege(token, SEC_PRIV_BACKUP)) {
+ max_bits |= ~(SEC_RIGHTS_PRIV_BACKUP);
}
if (*access_mask & ~max_bits) {
+ DEBUG(0,(__location__ " denied access to '%s' - wanted 0x%08x but got 0x%08x (missing 0x%08x)\n",
+ name?name->full_name:"(new file)", *access_mask, max_bits, *access_mask & ~max_bits));
return NT_STATUS_ACCESS_DENIED;
}
- *access_mask |= SEC_FILE_READ_ATTRIBUTE;
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ /* on SMB, this bit is always granted, even if not
+ asked for */
+ *access_mask |= SEC_FILE_READ_ATTRIBUTE;
+ }
return NT_STATUS_OK;
}
struct xattr_NTACL *acl;
NTSTATUS status;
struct security_descriptor *sd;
+ bool allow_delete = false;
+
+ /* on SMB2 a blank access mask is always denied */
+ if (pvfs->ntvfs->ctx->protocol >= PROTOCOL_SMB2_02 &&
+ *access_mask == 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
if (pvfs_read_only(pvfs, *access_mask)) {
return NT_STATUS_ACCESS_DENIED;
}
+ if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED ||
+ *access_mask & SEC_STD_DELETE) {
+ status = pvfs_access_check_parent(pvfs, req,
+ name, SEC_DIR_DELETE_CHILD);
+ if (NT_STATUS_IS_OK(status)) {
+ allow_delete = true;
+ *access_mask &= ~SEC_STD_DELETE;
+ }
+ }
+
acl = talloc(req, struct xattr_NTACL);
if (acl == NULL) {
return NT_STATUS_NO_MEMORY;
/* expand the generic access bits to file specific bits */
*access_mask = pvfs_translate_mask(*access_mask);
- *access_mask &= ~SEC_FILE_READ_ATTRIBUTE;
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ *access_mask &= ~SEC_FILE_READ_ATTRIBUTE;
+ }
status = pvfs_acl_load(pvfs, name, -1, acl);
if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
talloc_free(acl);
- return pvfs_access_check_unix(pvfs, req, name, access_mask);
+ status = pvfs_access_check_unix(pvfs, req, name, access_mask);
+ goto done;
}
if (!NT_STATUS_IS_OK(status)) {
return status;
}
/* check the acl against the required access mask */
- status = sec_access_check(sd, token, *access_mask, access_mask);
+ status = se_access_check(sd, token, *access_mask, access_mask);
+ talloc_free(acl);
+done:
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ /* on SMB, this bit is always granted, even if not
+ asked for */
+ *access_mask |= SEC_FILE_READ_ATTRIBUTE;
+ }
- /* this bit is always granted, even if not asked for */
- *access_mask |= SEC_FILE_READ_ATTRIBUTE;
+ if (allow_delete) {
+ *access_mask |= SEC_STD_DELETE;
+ }
- talloc_free(acl);
-
return status;
}
NTSTATUS pvfs_access_check_create(struct pvfs_state *pvfs,
struct ntvfs_request *req,
struct pvfs_filename *name,
- uint32_t *access_mask)
+ uint32_t *access_mask,
+ bool container,
+ struct security_descriptor **sd)
{
struct pvfs_filename *parent;
NTSTATUS status;
+ uint32_t parent_mask;
+ bool allow_delete = false;
+
+ if (pvfs_read_only(pvfs, *access_mask)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
status = pvfs_resolve_parent(pvfs, req, name, &parent);
- if (!NT_STATUS_IS_OK(status)) {
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (container) {
+ parent_mask = SEC_DIR_ADD_SUBDIR;
+ } else {
+ parent_mask = SEC_DIR_ADD_FILE;
+ }
+ if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED ||
+ *access_mask & SEC_STD_DELETE) {
+ parent_mask |= SEC_DIR_DELETE_CHILD;
+ }
+
+ status = pvfs_access_check(pvfs, req, parent, &parent_mask);
+ if (NT_STATUS_IS_OK(status)) {
+ if (parent_mask & SEC_DIR_DELETE_CHILD) {
+ allow_delete = true;
+ }
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ /*
+ * on ACCESS_DENIED we get the rejected bits
+ * remove the non critical SEC_DIR_DELETE_CHILD
+ * and check if something else was rejected.
+ */
+ parent_mask &= ~SEC_DIR_DELETE_CHILD;
+ if (parent_mask != 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ status = NT_STATUS_OK;
+ } else {
return status;
}
- status = pvfs_access_check(pvfs, req, parent, access_mask);
+ if (*sd == NULL) {
+ status = pvfs_acl_inherited_sd(pvfs, req, req, parent, container, sd);
+ }
+
+ talloc_free(parent);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
- if (! ((*access_mask) & SEC_DIR_ADD_FILE)) {
- return pvfs_access_check_simple(pvfs, req, parent, SEC_DIR_ADD_FILE);
+ /* expand the generic access bits to file specific bits */
+ *access_mask = pvfs_translate_mask(*access_mask);
+
+ if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED) {
+ *access_mask |= SEC_RIGHTS_FILE_ALL;
+ *access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED;
}
- return status;
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ /* on SMB, this bit is always granted, even if not
+ asked for */
+ *access_mask |= SEC_FILE_READ_ATTRIBUTE;
+ }
+
+ if (allow_delete) {
+ *access_mask |= SEC_STD_DELETE;
+ }
+
+ return NT_STATUS_OK;
}
/*
/*
- setup an ACL on a new file/directory based on the inherited ACL from
- the parent. If there is no inherited ACL then we don't set anything,
- as the default ACL applies anyway
+ calculate the ACL on a new file/directory based on the inherited ACL
+ from the parent. If there is no inherited ACL then return a NULL
+ ACL, which means the default ACL should be used
*/
-NTSTATUS pvfs_acl_inherit(struct pvfs_state *pvfs,
- struct ntvfs_request *req,
- struct pvfs_filename *name,
- int fd)
+NTSTATUS pvfs_acl_inherited_sd(struct pvfs_state *pvfs,
+ TALLOC_CTX *mem_ctx,
+ struct ntvfs_request *req,
+ struct pvfs_filename *parent,
+ bool container,
+ struct security_descriptor **ret_sd)
{
struct xattr_NTACL *acl;
NTSTATUS status;
- struct pvfs_filename *parent;
struct security_descriptor *parent_sd, *sd;
- bool container;
+ struct id_map *ids;
+ struct composite_context *ctx;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
- /* form the parents path */
- status = pvfs_resolve_parent(pvfs, req, name, &parent);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
+ *ret_sd = NULL;
acl = talloc(req, struct xattr_NTACL);
- if (acl == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
+ NT_STATUS_HAVE_NO_MEMORY_AND_FREE(acl, tmp_ctx);
status = pvfs_acl_load(pvfs, parent, -1, acl);
if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
+ NT_STATUS_NOT_OK_RETURN_AND_FREE(status, tmp_ctx);
switch (acl->version) {
case 1:
parent_sd = acl->info.sd;
break;
default:
+ talloc_free(tmp_ctx);
return NT_STATUS_INVALID_ACL;
}
parent_sd->dacl == NULL ||
parent_sd->dacl->num_aces == 0) {
/* go with the default ACL */
+ talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
/* create the new sd */
sd = security_descriptor_initialise(req);
- if (sd == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
+ NT_STATUS_HAVE_NO_MEMORY_AND_FREE(sd, tmp_ctx);
- status = sidmap_uid_to_sid(pvfs->sidmap, sd, name->st.st_uid, &sd->owner_sid);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
- status = sidmap_gid_to_sid(pvfs->sidmap, sd, name->st.st_gid, &sd->group_sid);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
+ ids = talloc_array(sd, struct id_map, 2);
+ NT_STATUS_HAVE_NO_MEMORY_AND_FREE(ids, tmp_ctx);
- sd->type |= SEC_DESC_DACL_PRESENT;
+ ids[0].xid.id = geteuid();
+ ids[0].xid.type = ID_TYPE_UID;
+ ids[0].sid = NULL;
+ ids[0].status = ID_UNKNOWN;
- container = (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) ? true:false;
+ ids[1].xid.id = getegid();
+ ids[1].xid.type = ID_TYPE_GID;
+ ids[1].sid = NULL;
+ ids[1].status = ID_UNKNOWN;
+
+ ctx = wbc_xids_to_sids_send(pvfs->wbc_ctx, ids, 2, ids);
+ NT_STATUS_HAVE_NO_MEMORY_AND_FREE(ctx, tmp_ctx);
+
+ status = wbc_xids_to_sids_recv(ctx, &ids);
+ NT_STATUS_NOT_OK_RETURN_AND_FREE(status, tmp_ctx);
+
+ sd->owner_sid = talloc_steal(sd, ids[0].sid);
+ sd->group_sid = talloc_steal(sd, ids[1].sid);
+
+ sd->type |= SEC_DESC_DACL_PRESENT;
/* fill in the aces from the parent */
status = pvfs_acl_inherit_aces(pvfs, parent_sd, sd, container);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
+ NT_STATUS_NOT_OK_RETURN_AND_FREE(status, tmp_ctx);
/* if there is nothing to inherit then we fallback to the
default acl */
if (sd->dacl == NULL || sd->dacl->num_aces == 0) {
+ talloc_free(tmp_ctx);
return NT_STATUS_OK;
}
- acl->info.sd = sd;
+ *ret_sd = talloc_steal(mem_ctx, sd);
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+
+/*
+ setup an ACL on a new file/directory based on the inherited ACL from
+ the parent. If there is no inherited ACL then we don't set anything,
+ as the default ACL applies anyway
+*/
+NTSTATUS pvfs_acl_inherit(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ int fd)
+{
+ struct xattr_NTACL acl;
+ NTSTATUS status;
+ struct security_descriptor *sd;
+ struct pvfs_filename *parent;
+ bool container;
+
+ /* form the parents path */
+ status = pvfs_resolve_parent(pvfs, req, name, &parent);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ container = (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) ? true:false;
+
+ status = pvfs_acl_inherited_sd(pvfs, req, req, parent, container, &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(parent);
+ return status;
+ }
+
+ if (sd == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ acl.version = 1;
+ acl.info.sd = sd;
+
+ status = pvfs_acl_save(pvfs, name, fd, &acl);
+ talloc_free(sd);
+ talloc_free(parent);
- status = pvfs_acl_save(pvfs, name, fd, acl);
-
return status;
}
+
+/*
+ return the maximum allowed access mask
+*/
+NTSTATUS pvfs_access_maximal_allowed(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ uint32_t *maximal_access)
+{
+ *maximal_access = SEC_FLAG_MAXIMUM_ALLOWED;
+ return pvfs_access_check(pvfs, req, name, maximal_access);
+}