/* * Convert NFSv4 acls stored per http://www.suse.de/~agruen/nfs4acl/ to NT acls and vice versa. * * Copyright (C) Jiri Sasek, 2007 * based on the foobar.c module which is copyrighted by Volker Lendecke * based on pvfs_acl_nfs4.c Copyright (C) Andrew Tridgell 2006 * * based on vfs_fake_acls: * Copyright (C) Tim Potter, 1999-2000 * Copyright (C) Alexander Bokovoy, 2002 * Copyright (C) Andrew Bartlett, 2002,2012 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * */ #include "includes.h" #include "system/filesys.h" #include "smbd/smbd.h" #include "nfs4_acls.h" #include "librpc/gen_ndr/ndr_nfs4acl.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_VFS static struct nfs4acl *nfs4acl_blob2acl(DATA_BLOB *blob, TALLOC_CTX *mem_ctx) { enum ndr_err_code ndr_err; struct nfs4acl *acl = talloc(mem_ctx, struct nfs4acl); if (!acl) { errno = ENOMEM; return NULL; } ndr_err = ndr_pull_struct_blob(blob, acl, acl, (ndr_pull_flags_fn_t)ndr_pull_nfs4acl); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { DEBUG(0, ("ndr_pull_acl_t failed: %s\n", ndr_errstr(ndr_err))); TALLOC_FREE(acl); return NULL; } return acl; } static DATA_BLOB nfs4acl_acl2blob(TALLOC_CTX *mem_ctx, struct nfs4acl *acl) { enum ndr_err_code ndr_err; DATA_BLOB blob; ndr_err = ndr_push_struct_blob(&blob, mem_ctx, acl, (ndr_push_flags_fn_t)ndr_push_nfs4acl); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { DEBUG(0, ("ndr_push_acl_t failed: %s\n", ndr_errstr(ndr_err))); return data_blob_null; } return blob; } static NTSTATUS nfs4_get_nfs4_acl_common(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct SMB4ACL_T **ppacl) { int i; struct nfs4acl *nfs4acl = NULL; struct SMB4ACL_T *pacl = NULL; TALLOC_CTX *frame = talloc_stackframe(); nfs4acl = nfs4acl_blob2acl(blob, frame); /* create SMB4ACL data */ if((pacl = smb_create_smb4acl(mem_ctx)) == NULL) { TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } for(i=0; ia_count; i++) { SMB_ACE4PROP_T aceprop; aceprop.aceType = (uint32_t) nfs4acl->ace[i].e_type; aceprop.aceFlags = (uint32_t) nfs4acl->ace[i].e_flags; aceprop.aceMask = (uint32_t) nfs4acl->ace[i].e_mask; aceprop.who.id = (uint32_t) nfs4acl->ace[i].e_id; if (!strcmp(nfs4acl->ace[i].e_who, NFS4ACL_XATTR_OWNER_WHO)) { aceprop.flags = SMB_ACE4_ID_SPECIAL; aceprop.who.special_id = SMB_ACE4_WHO_OWNER; } else if (!strcmp(nfs4acl->ace[i].e_who, NFS4ACL_XATTR_GROUP_WHO)) { aceprop.flags = SMB_ACE4_ID_SPECIAL; aceprop.who.special_id = SMB_ACE4_WHO_GROUP; } else if (!strcmp(nfs4acl->ace[i].e_who, NFS4ACL_XATTR_EVERYONE_WHO)) { aceprop.flags = SMB_ACE4_ID_SPECIAL; aceprop.who.special_id = SMB_ACE4_WHO_EVERYONE; } else { aceprop.flags = 0; } if(smb_add_ace4(pacl, &aceprop) == NULL) { TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } } *ppacl = pacl; TALLOC_FREE(frame); return NT_STATUS_OK; } /* Fetch the NFSv4 ACL from the xattr, and convert into Samba's internal NFSv4 format */ static NTSTATUS nfs4_fget_nfs4_acl(vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, files_struct *fsp, struct SMB4ACL_T **ppacl) { NTSTATUS status; DATA_BLOB blob = data_blob_null; ssize_t length; TALLOC_CTX *frame = talloc_stackframe(); do { blob.length += 1000; blob.data = talloc_realloc(frame, blob.data, uint8_t, blob.length); if (!blob.data) { TALLOC_FREE(frame); errno = ENOMEM; return NT_STATUS_NO_MEMORY; } length = SMB_VFS_NEXT_FGETXATTR(handle, fsp, NFS4ACL_XATTR_NAME, blob.data, blob.length); blob.length = length; } while (length == -1 && errno == ERANGE); if (length == -1) { TALLOC_FREE(frame); return map_nt_error_from_unix(errno); } status = nfs4_get_nfs4_acl_common(mem_ctx, &blob, ppacl); TALLOC_FREE(frame); return status; } /* Fetch the NFSv4 ACL from the xattr, and convert into Samba's internal NFSv4 format */ static NTSTATUS nfs4_get_nfs4_acl(vfs_handle_struct *handle, TALLOC_CTX *mem_ctx, const char *path, struct SMB4ACL_T **ppacl) { NTSTATUS status; DATA_BLOB blob = data_blob_null; ssize_t length; TALLOC_CTX *frame = talloc_stackframe(); do { blob.length += 1000; blob.data = talloc_realloc(frame, blob.data, uint8_t, blob.length); if (!blob.data) { TALLOC_FREE(frame); errno = ENOMEM; return NT_STATUS_NO_MEMORY; } length = SMB_VFS_NEXT_GETXATTR(handle, path, NFS4ACL_XATTR_NAME, blob.data, blob.length); blob.length = length; } while (length == -1 && errno == ERANGE); if (length == -1) { TALLOC_FREE(frame); return map_nt_error_from_unix(errno); } status = nfs4_get_nfs4_acl_common(mem_ctx, &blob, ppacl); TALLOC_FREE(frame); return status; } static bool nfs4acl_smb4acl2nfs4acl(TALLOC_CTX *mem_ctx, struct SMB4ACL_T *smbacl, struct nfs4acl **pnfs4acl, bool denymissingspecial) { struct nfs4acl *nfs4acl; struct SMB4ACE_T *smbace; bool have_special_id = false; int i; /* allocate the field of NFS4 aces */ nfs4acl = talloc_zero(mem_ctx, struct nfs4acl); if(nfs4acl == NULL) { errno = ENOMEM; return false; } nfs4acl->a_count = smb_get_naces(smbacl); nfs4acl->ace = talloc_zero_array(nfs4acl, struct nfs4ace, nfs4acl->a_count); if(nfs4acl->ace == NULL) { TALLOC_FREE(nfs4acl); errno = ENOMEM; return false; } /* handle all aces */ for(smbace = smb_first_ace4(smbacl), i = 0; smbace!=NULL; smbace = smb_next_ace4(smbace), i++) { SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace); nfs4acl->ace[i].e_type = aceprop->aceType; nfs4acl->ace[i].e_flags = aceprop->aceFlags; nfs4acl->ace[i].e_mask = aceprop->aceMask; nfs4acl->ace[i].e_id = aceprop->who.id; if(aceprop->flags & SMB_ACE4_ID_SPECIAL) { switch(aceprop->who.special_id) { case SMB_ACE4_WHO_EVERYONE: nfs4acl->ace[i].e_who = NFS4ACL_XATTR_EVERYONE_WHO; break; case SMB_ACE4_WHO_OWNER: nfs4acl->ace[i].e_who = NFS4ACL_XATTR_OWNER_WHO; break; case SMB_ACE4_WHO_GROUP: nfs4acl->ace[i].e_who = NFS4ACL_XATTR_GROUP_WHO; break; default: DEBUG(8, ("unsupported special_id %d\n", \ aceprop->who.special_id)); continue; /* don't add it !!! */ } have_special_id = true; } else { nfs4acl->ace[i].e_who = ""; } } if (!have_special_id && denymissingspecial) { TALLOC_FREE(nfs4acl); errno = EACCES; return false; } SMB_ASSERT(i == nfs4acl->a_count); *pnfs4acl = nfs4acl; return true; } static bool nfs4acl_xattr_set_smb4acl(vfs_handle_struct *handle, const char *path, struct SMB4ACL_T *smbacl) { TALLOC_CTX *frame = talloc_stackframe(); struct nfs4acl *nfs4acl; int ret; bool denymissingspecial; DATA_BLOB blob; denymissingspecial = lp_parm_bool(handle->conn->params->service, "nfs4acl_xattr", "denymissingspecial", false); if (!nfs4acl_smb4acl2nfs4acl(frame, smbacl, &nfs4acl, denymissingspecial)) { DEBUG(0, ("Failed to convert smb ACL to nfs4 ACL.\n")); TALLOC_FREE(frame); return false; } blob = nfs4acl_acl2blob(frame, nfs4acl); if (!blob.data) { DEBUG(0, ("Failed to convert ACL to linear blob for xattr\n")); TALLOC_FREE(frame); errno = EINVAL; return false; } ret = SMB_VFS_NEXT_SETXATTR(handle, path, NFS4ACL_XATTR_NAME, blob.data, blob.length, 0); if (ret != 0) { DEBUG(0, ("can't store acl in xattr: %s\n", strerror(errno))); } TALLOC_FREE(frame); return ret == 0; } /* call-back function processing the NT acl -> NFS4 acl using NFSv4 conv. */ static bool nfs4acl_xattr_fset_smb4acl(vfs_handle_struct *handle, files_struct *fsp, struct SMB4ACL_T *smbacl) { TALLOC_CTX *frame = talloc_stackframe(); struct nfs4acl *nfs4acl; int ret; bool denymissingspecial; DATA_BLOB blob; denymissingspecial = lp_parm_bool(fsp->conn->params->service, "nfs4acl_xattr", "denymissingspecial", false); if (!nfs4acl_smb4acl2nfs4acl(frame, smbacl, &nfs4acl, denymissingspecial)) { DEBUG(0, ("Failed to convert smb ACL to nfs4 ACL.\n")); TALLOC_FREE(frame); return false; } blob = nfs4acl_acl2blob(frame, nfs4acl); if (!blob.data) { DEBUG(0, ("Failed to convert ACL to linear blob for xattr\n")); TALLOC_FREE(frame); errno = EINVAL; return false; } if (fsp->fh->fd == -1) { DEBUG(0, ("Error: fsp->fh->fd == -1\n")); } ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, NFS4ACL_XATTR_NAME, blob.data, blob.length, 0); if (ret != 0) { DEBUG(0, ("can't store acl in xattr: %s\n", strerror(errno))); } TALLOC_FREE(frame); return ret == 0; } /* nfs4_set_nt_acl() * set the local file's acls obtaining it in NT form * using the NFSv4 format conversion */ static NTSTATUS nfs4_set_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd) { return smb_set_nt_acl_nfs4(handle, fsp, security_info_sent, psd, nfs4acl_xattr_fset_smb4acl); } static struct SMB4ACL_T *nfs4acls_defaultacl(TALLOC_CTX *mem_ctx) { struct SMB4ACL_T *pacl = NULL; struct SMB4ACE_T *pace; SMB_ACE4PROP_T ace = { .flags = SMB_ACE4_ID_SPECIAL, .who = { .id = SMB_ACE4_WHO_EVERYONE, }, .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, .aceFlags = 0, .aceMask = SMB_ACE4_ALL_MASKS, }; DEBUG(10, ("Building default full access acl\n")); pacl = smb_create_smb4acl(mem_ctx); if (pacl == NULL) { DEBUG(0, ("talloc failed\n")); errno = ENOMEM; return NULL; } pace = smb_add_ace4(pacl, &ace); if (pace == NULL) { DEBUG(0, ("talloc failed\n")); TALLOC_FREE(pacl); errno = ENOMEM; return NULL; } return pacl; } /* * Because there is no good way to guarantee that a new xattr will be * created on file creation there might be no acl xattr on a file when * trying to read the acl. In this case the acl xattr will get * constructed at that time from the parent acl. * If the parent ACL doesn't have an xattr either the call will * recurse to the next parent directory until the share root is * reached. If the share root doesn't contain an ACL xattr either a * default ACL will be used. * Also a default ACL will be set if a non inheriting ACL is encountered. * * Basic algorithm: * read acl xattr blob * if acl xattr blob doesn't exist * stat current directory to know if it's a file or directory * read acl xattr blob from parent dir * acl xattr blob to smb nfs4 acl * calculate inherited smb nfs4 acl * without inheritance use default smb nfs4 acl * smb nfs4 acl to acl xattr blob * set acl xattr blob * return smb nfs4 acl * else * acl xattr blob to smb nfs4 acl * * Todo: Really use mem_ctx after fixing interface of nfs4_acls */ static struct SMB4ACL_T *nfs4acls_inheritacl(vfs_handle_struct *handle, const char *path, TALLOC_CTX *mem_ctx) { char *parent_dir = NULL; struct SMB4ACL_T *pparentacl = NULL; struct SMB4ACL_T *pchildacl = NULL; struct SMB4ACE_T *pace; SMB_ACE4PROP_T ace; bool isdir; struct smb_filename *smb_fname = NULL; NTSTATUS status; int ret; TALLOC_CTX *frame = talloc_stackframe(); DEBUG(10, ("nfs4acls_inheritacl invoked for %s\n", path)); smb_fname = synthetic_smb_fname(frame, path, NULL, NULL, 0); if (smb_fname == NULL) { TALLOC_FREE(frame); errno = ENOMEM; return NULL; } ret = SMB_VFS_STAT(handle->conn, smb_fname); if (ret == -1) { DEBUG(0,("nfs4acls_inheritacl: failed to stat " "directory %s. Error was %s\n", smb_fname_str_dbg(smb_fname), strerror(errno))); TALLOC_FREE(frame); return NULL; } isdir = S_ISDIR(smb_fname->st.st_ex_mode); if (!parent_dirname(talloc_tos(), path, &parent_dir, NULL)) { TALLOC_FREE(frame); errno = ENOMEM; return NULL; } status = nfs4_get_nfs4_acl(handle, frame, parent_dir, &pparentacl); if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) && strncmp(parent_dir, ".", 2) != 0) { pparentacl = nfs4acls_inheritacl(handle, parent_dir, frame); } else if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { pparentacl = nfs4acls_defaultacl(frame); } else if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(frame); return NULL; } pchildacl = smb_create_smb4acl(mem_ctx); if (pchildacl == NULL) { DEBUG(0, ("talloc failed\n")); TALLOC_FREE(frame); errno = ENOMEM; return NULL; } for (pace = smb_first_ace4(pparentacl); pace != NULL; pace = smb_next_ace4(pace)) { struct SMB4ACE_T *pchildace; ace = *smb_get_ace4(pace); if ((isdir && !(ace.aceFlags & SMB_ACE4_DIRECTORY_INHERIT_ACE)) || (!isdir && !(ace.aceFlags & SMB_ACE4_FILE_INHERIT_ACE))) { DEBUG(10, ("non inheriting ace type: %d, iflags: %x, " "flags: %x, mask: %x, who: %d\n", ace.aceType, ace.flags, ace.aceFlags, ace.aceMask, ace.who.id)); continue; } DEBUG(10, ("inheriting ace type: %d, iflags: %x, " "flags: %x, mask: %x, who: %d\n", ace.aceType, ace.flags, ace.aceFlags, ace.aceMask, ace.who.id)); ace.aceFlags |= SMB_ACE4_INHERITED_ACE; if (ace.aceFlags & SMB_ACE4_INHERIT_ONLY_ACE) { ace.aceFlags &= ~SMB_ACE4_INHERIT_ONLY_ACE; } if (ace.aceFlags & SMB_ACE4_NO_PROPAGATE_INHERIT_ACE) { ace.aceFlags &= ~SMB_ACE4_FILE_INHERIT_ACE; ace.aceFlags &= ~SMB_ACE4_DIRECTORY_INHERIT_ACE; ace.aceFlags &= ~SMB_ACE4_NO_PROPAGATE_INHERIT_ACE; } pchildace = smb_add_ace4(pchildacl, &ace); if (pchildace == NULL) { DEBUG(0, ("talloc failed\n")); TALLOC_FREE(frame); errno = ENOMEM; return NULL; } } /* Set a default ACL if we didn't inherit anything. */ if (smb_first_ace4(pchildacl) == NULL) { TALLOC_FREE(pchildacl); pchildacl = nfs4acls_defaultacl(mem_ctx); } /* store the returned ACL to get it directly in the future and avoid dynamic inheritance behavior. */ nfs4acl_xattr_set_smb4acl(handle, path, pchildacl); TALLOC_FREE(frame); return pchildacl; } static NTSTATUS nfs4acl_xattr_fget_nt_acl(struct vfs_handle_struct *handle, struct files_struct *fsp, uint32_t security_info, TALLOC_CTX *mem_ctx, struct security_descriptor **ppdesc) { struct SMB4ACL_T *pacl; NTSTATUS status; TALLOC_CTX *frame = talloc_stackframe(); status = nfs4_fget_nfs4_acl(handle, frame, fsp, &pacl); if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { pacl = nfs4acls_inheritacl(handle, fsp->fsp_name->base_name, frame); } else if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(frame); return status; } status = smb_fget_nt_acl_nfs4(fsp, NULL, security_info, mem_ctx, ppdesc, pacl); TALLOC_FREE(frame); return status; } static NTSTATUS nfs4acl_xattr_get_nt_acl(struct vfs_handle_struct *handle, const struct smb_filename *smb_fname, uint32_t security_info, TALLOC_CTX *mem_ctx, struct security_descriptor **ppdesc) { struct SMB4ACL_T *pacl; NTSTATUS status; const char *name = smb_fname->base_name; TALLOC_CTX *frame = talloc_stackframe(); status = nfs4_get_nfs4_acl(handle, frame, name, &pacl); if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { pacl = nfs4acls_inheritacl(handle, name, frame); } else if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(frame); return status; } status = smb_get_nt_acl_nfs4(handle->conn, smb_fname, NULL, security_info, mem_ctx, ppdesc, pacl); TALLOC_FREE(frame); return status; } static NTSTATUS nfs4acl_xattr_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd) { return nfs4_set_nt_acl(handle, fsp, security_info_sent, psd); } /* As long as Samba does not support an exiplicit method for a module to define conflicting vfs methods, we should override all conflicting methods here. That way, we know we are using the NFSv4 storage Function declarations taken from vfs_solarisacl */ static SMB_ACL_T nfs4acl_xattr_fail__sys_acl_get_file(vfs_handle_struct *handle, const char *path_p, SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx) { return (SMB_ACL_T)NULL; } static SMB_ACL_T nfs4acl_xattr_fail__sys_acl_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx) { return (SMB_ACL_T)NULL; } static int nfs4acl_xattr_fail__sys_acl_set_file(vfs_handle_struct *handle, const char *name, SMB_ACL_TYPE_T type, SMB_ACL_T theacl) { return -1; } static int nfs4acl_xattr_fail__sys_acl_set_fd(vfs_handle_struct *handle, files_struct *fsp, SMB_ACL_T theacl) { return -1; } static int nfs4acl_xattr_fail__sys_acl_delete_def_file(vfs_handle_struct *handle, const char *path) { return -1; } static int nfs4acl_xattr_fail__sys_acl_blob_get_file(vfs_handle_struct *handle, const char *path_p, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob) { return -1; } static int nfs4acl_xattr_fail__sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob) { return -1; } /* VFS operations structure */ static struct vfs_fn_pointers nfs4acl_xattr_fns = { .sys_acl_get_file_fn = nfs4acl_xattr_fail__sys_acl_get_file, .sys_acl_get_fd_fn = nfs4acl_xattr_fail__sys_acl_get_fd, .sys_acl_blob_get_file_fn = nfs4acl_xattr_fail__sys_acl_blob_get_file, .sys_acl_blob_get_fd_fn = nfs4acl_xattr_fail__sys_acl_blob_get_fd, .sys_acl_set_file_fn = nfs4acl_xattr_fail__sys_acl_set_file, .sys_acl_set_fd_fn = nfs4acl_xattr_fail__sys_acl_set_fd, .sys_acl_delete_def_file_fn = nfs4acl_xattr_fail__sys_acl_delete_def_file, .fget_nt_acl_fn = nfs4acl_xattr_fget_nt_acl, .get_nt_acl_fn = nfs4acl_xattr_get_nt_acl, .fset_nt_acl_fn = nfs4acl_xattr_fset_nt_acl, }; NTSTATUS vfs_nfs4acl_xattr_init(void); NTSTATUS vfs_nfs4acl_xattr_init(void) { return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "nfs4acl_xattr", &nfs4acl_xattr_fns); }