/* * Fake ACLs VFS module. Implements passthrough operation of all VFS * calls to disk functions, except for file ownership and ACLs, which * are stored in xattrs. * * 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 "smbd/smbd.h" #include "system/filesys.h" #include "auth.h" #include "librpc/gen_ndr/ndr_smb_acl.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_VFS #define FAKE_UID "system.fake_uid" #define FAKE_GID "system.fake_gid" #define FAKE_ACL_ACCESS_XATTR "system.fake_access_acl" #define FAKE_ACL_DEFAULT_XATTR "system.fake_default_acl" static int fake_acls_uid(vfs_handle_struct *handle, struct smb_filename *smb_fname, uid_t *uid) { ssize_t size; uint8_t uid_buf[4]; size = SMB_VFS_NEXT_GETXATTR(handle, smb_fname, FAKE_UID, uid_buf, sizeof(uid_buf)); if (size == -1 && errno == ENOATTR) { return 0; } if (size != 4) { return -1; } *uid = IVAL(uid_buf, 0); return 0; } static int fake_acls_gid(vfs_handle_struct *handle, struct smb_filename *smb_fname, uid_t *gid) { ssize_t size; uint8_t gid_buf[4]; size = SMB_VFS_NEXT_GETXATTR(handle, smb_fname, FAKE_GID, gid_buf, sizeof(gid_buf)); if (size == -1 && errno == ENOATTR) { return 0; } if (size != 4) { return -1; } *gid = IVAL(gid_buf, 0); return 0; } static int fake_acls_fuid(vfs_handle_struct *handle, files_struct *fsp, uid_t *uid) { ssize_t size; uint8_t uid_buf[4]; size = SMB_VFS_NEXT_FGETXATTR(handle, fsp, FAKE_UID, uid_buf, sizeof(uid_buf)); if (size == -1 && errno == ENOATTR) { return 0; } if (size != 4) { return -1; } *uid = IVAL(uid_buf, 0); return 0; } static int fake_acls_fgid(vfs_handle_struct *handle, files_struct *fsp, uid_t *gid) { ssize_t size; uint8_t gid_buf[4]; size = SMB_VFS_NEXT_FGETXATTR(handle, fsp, FAKE_GID, gid_buf, sizeof(gid_buf)); if (size == -1 && errno == ENOATTR) { return 0; } if (size != 4) { return -1; } *gid = IVAL(gid_buf, 0); return 0; } static int fake_acls_stat(vfs_handle_struct *handle, struct smb_filename *smb_fname) { int ret = -1; ret = SMB_VFS_NEXT_STAT(handle, smb_fname); if (ret == 0) { TALLOC_CTX *frame = talloc_stackframe(); char *path; struct smb_filename smb_fname_base = { .base_name = smb_fname->base_name }; NTSTATUS status; /* * As we're calling getxattr directly here * we need to use only the base_name, not * the full name containing any stream name. */ status = get_full_smb_filename(frame, &smb_fname_base, &path); if (!NT_STATUS_IS_OK(status)) { errno = map_errno_from_nt_status(status); TALLOC_FREE(frame); return -1; } ret = fake_acls_uid(handle, &smb_fname_base, &smb_fname->st.st_ex_uid); if (ret != 0) { TALLOC_FREE(frame); return ret; } ret = fake_acls_gid(handle, &smb_fname_base, &smb_fname->st.st_ex_gid); if (ret != 0) { TALLOC_FREE(frame); return ret; } TALLOC_FREE(frame); } return ret; } static int fake_acls_lstat(vfs_handle_struct *handle, struct smb_filename *smb_fname) { int ret = -1; ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); if (ret == 0) { TALLOC_CTX *frame = talloc_stackframe(); char *path; struct smb_filename smb_fname_base = { .base_name = smb_fname->base_name }; NTSTATUS status; /* * As we're calling getxattr directly here * we need to use only the base_name, not * the full name containing any stream name. */ status = get_full_smb_filename(frame, &smb_fname_base, &path); if (!NT_STATUS_IS_OK(status)) { errno = map_errno_from_nt_status(status); TALLOC_FREE(frame); return -1; } /* This isn't quite right (calling getxattr not * lgetxattr), but for the test purposes of this * module (fake NT ACLs from windows clients), it is * close enough. We removed the l*xattr functions * because linux doesn't support using them, but we * could fake them in xattr_tdb if we really wanted * to. We ignore errors because the link might not point anywhere */ fake_acls_uid(handle, &smb_fname_base, &smb_fname->st.st_ex_uid); fake_acls_gid(handle, &smb_fname_base, &smb_fname->st.st_ex_gid); TALLOC_FREE(frame); } return ret; } static int fake_acls_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf) { int ret = -1; ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); if (ret == 0) { ret = fake_acls_fuid(handle, fsp, &sbuf->st_ex_uid); if (ret != 0) { return ret; } ret = fake_acls_fgid(handle, fsp, &sbuf->st_ex_gid); if (ret != 0) { return ret; } } return ret; } static SMB_ACL_T fake_acls_blob2acl(DATA_BLOB *blob, TALLOC_CTX *mem_ctx) { enum ndr_err_code ndr_err; struct smb_acl_t *acl = talloc(mem_ctx, struct smb_acl_t); if (!acl) { errno = ENOMEM; return NULL; } ndr_err = ndr_pull_struct_blob(blob, acl, acl, (ndr_pull_flags_fn_t)ndr_pull_smb_acl_t); 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 fake_acls_acl2blob(TALLOC_CTX *mem_ctx, SMB_ACL_T 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_smb_acl_t); 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 SMB_ACL_T fake_acls_sys_acl_get_file(struct vfs_handle_struct *handle, const struct smb_filename *smb_fname, SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx) { DATA_BLOB blob = data_blob_null; ssize_t length; const char *name = NULL; struct smb_acl_t *acl = NULL; TALLOC_CTX *frame = talloc_stackframe(); switch (type) { case SMB_ACL_TYPE_ACCESS: name = FAKE_ACL_ACCESS_XATTR; break; case SMB_ACL_TYPE_DEFAULT: name = FAKE_ACL_DEFAULT_XATTR; break; } do { blob.length += 1000; blob.data = talloc_realloc(frame, blob.data, uint8_t, blob.length); if (!blob.data) { errno = ENOMEM; TALLOC_FREE(frame); return NULL; } length = SMB_VFS_NEXT_GETXATTR(handle, smb_fname, name, blob.data, blob.length); blob.length = length; } while (length == -1 && errno == ERANGE); if (length == -1 && errno == ENOATTR) { TALLOC_FREE(frame); return NULL; } if (length != -1) { acl = fake_acls_blob2acl(&blob, mem_ctx); } TALLOC_FREE(frame); return acl; } static SMB_ACL_T fake_acls_sys_acl_get_fd(struct vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx) { DATA_BLOB blob = data_blob_null; ssize_t length; const char *name = FAKE_ACL_ACCESS_XATTR; struct smb_acl_t *acl = NULL; TALLOC_CTX *frame = talloc_stackframe(); do { blob.length += 1000; blob.data = talloc_realloc(frame, blob.data, uint8_t, blob.length); if (!blob.data) { errno = ENOMEM; TALLOC_FREE(frame); return NULL; } length = SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, blob.data, blob.length); blob.length = length; } while (length == -1 && errno == ERANGE); if (length == -1 && errno == ENOATTR) { TALLOC_FREE(frame); return NULL; } if (length != -1) { acl = fake_acls_blob2acl(&blob, mem_ctx); } TALLOC_FREE(frame); return acl; } static int fake_acls_sys_acl_set_file(vfs_handle_struct *handle, const struct smb_filename *smb_fname, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) { int ret; const char *name = NULL; TALLOC_CTX *frame = talloc_stackframe(); DATA_BLOB blob = fake_acls_acl2blob(frame, theacl); if (!blob.data) { DEBUG(0, ("Failed to convert ACL to linear blob for xattr storage\n")); TALLOC_FREE(frame); errno = EINVAL; return -1; } switch (acltype) { case SMB_ACL_TYPE_ACCESS: name = FAKE_ACL_ACCESS_XATTR; break; case SMB_ACL_TYPE_DEFAULT: name = FAKE_ACL_DEFAULT_XATTR; break; } ret = SMB_VFS_NEXT_FSETXATTR(handle, smb_fname->fsp, name, blob.data, blob.length, 0); TALLOC_FREE(frame); return ret; } static int fake_acls_sys_acl_set_fd(vfs_handle_struct *handle, struct files_struct *fsp, SMB_ACL_TYPE_T type, SMB_ACL_T theacl) { int ret; const char *name = NULL; TALLOC_CTX *frame = talloc_stackframe(); DATA_BLOB blob = fake_acls_acl2blob(frame, theacl); if (!blob.data) { DEBUG(0, ("Failed to convert ACL to linear blob for xattr storage\n")); TALLOC_FREE(frame); errno = EINVAL; return -1; } switch (type) { case SMB_ACL_TYPE_ACCESS: name = FAKE_ACL_ACCESS_XATTR; break; case SMB_ACL_TYPE_DEFAULT: name = FAKE_ACL_DEFAULT_XATTR; break; default: errno = EINVAL; return -1; } ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, blob.data, blob.length, 0); TALLOC_FREE(frame); return ret; } static int fake_acls_sys_acl_delete_def_file(vfs_handle_struct *handle, const struct smb_filename *smb_fname_in) { int ret; const char *name = FAKE_ACL_DEFAULT_XATTR; TALLOC_CTX *frame = talloc_stackframe(); struct smb_filename *smb_fname = cp_smb_filename_nostream(talloc_tos(), smb_fname_in); if (smb_fname == NULL) { TALLOC_FREE(frame); errno = ENOMEM; return -1; } ret = SMB_VFS_NEXT_STAT(handle, smb_fname); if (ret == -1) { TALLOC_FREE(frame); return -1; } if (!S_ISDIR(smb_fname->st.st_ex_mode)) { errno = EINVAL; TALLOC_FREE(frame); return -1; } ret = SMB_VFS_NEXT_REMOVEXATTR(handle, smb_fname, name); if (ret == -1 && errno == ENOATTR) { ret = 0; errno = 0; } TALLOC_FREE(frame); return ret; } static int fake_acls_lchown(vfs_handle_struct *handle, const struct smb_filename *smb_fname, uid_t uid, gid_t gid) { int ret; uint8_t id_buf[4]; if (uid != -1) { uid_t current_uid = get_current_uid(handle->conn); if (current_uid != 0 && current_uid != uid) { return EACCES; } /* This isn't quite right (calling setxattr not * lsetxattr), but for the test purposes of this * module (fake NT ACLs from windows clients), it is * close enough. We removed the l*xattr functions * because linux doesn't support using them, but we * could fake them in xattr_tdb if we really wanted * to. */ SIVAL(id_buf, 0, uid); ret = SMB_VFS_NEXT_SETXATTR(handle, smb_fname, FAKE_UID, id_buf, sizeof(id_buf), 0); if (ret != 0) { return ret; } } if (gid != -1) { SIVAL(id_buf, 0, gid); ret = SMB_VFS_NEXT_SETXATTR(handle, smb_fname, FAKE_GID, id_buf, sizeof(id_buf), 0); if (ret != 0) { return ret; } } return 0; } static int fake_acls_fchown(vfs_handle_struct *handle, files_struct *fsp, uid_t uid, gid_t gid) { int ret; uint8_t id_buf[4]; if (uid != -1) { uid_t current_uid = get_current_uid(handle->conn); if (current_uid != 0 && current_uid != uid) { return EACCES; } SIVAL(id_buf, 0, uid); ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, FAKE_UID, id_buf, sizeof(id_buf), 0); if (ret != 0) { return ret; } } if (gid != -1) { SIVAL(id_buf, 0, gid); ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, FAKE_GID, id_buf, sizeof(id_buf), 0); if (ret != 0) { return ret; } } return 0; } /* * Implement the chmod uid/mask/other mode changes on a fake ACL. */ static int fake_acl_process_chmod(SMB_ACL_T *pp_the_acl, uid_t owner, mode_t mode) { bool got_mask = false; int entry_id = SMB_ACL_FIRST_ENTRY; mode_t umode = 0; mode_t mmode = 0; mode_t omode = 0; int ret = -1; SMB_ACL_T the_acl = *pp_the_acl; /* Split the mode into u/mask/other masks. */ umode = unix_perms_to_acl_perms(mode, S_IRUSR, S_IWUSR, S_IXUSR); mmode = unix_perms_to_acl_perms(mode, S_IRGRP, S_IWGRP, S_IXGRP); omode = unix_perms_to_acl_perms(mode, S_IROTH, S_IWOTH, S_IXOTH); while (1) { SMB_ACL_ENTRY_T entry; SMB_ACL_TAG_T tagtype; SMB_ACL_PERMSET_T permset; uid_t *puid = NULL; ret = sys_acl_get_entry(the_acl, entry_id, &entry); if (ret == 0) { /* End of ACL */ break; } if (ret == -1) { return -1; } ret = sys_acl_get_tag_type(entry, &tagtype); if (ret == -1) { return -1; } ret = sys_acl_get_permset(entry, &permset); if (ret == -1) { return -1; } switch (tagtype) { case SMB_ACL_USER_OBJ: ret = map_acl_perms_to_permset(umode, &permset); if (ret == -1) { return -1; } break; case SMB_ACL_USER: puid = (uid_t *)sys_acl_get_qualifier(entry); if (puid == NULL) { return -1; } if (owner != *puid) { break; } ret = map_acl_perms_to_permset(umode, &permset); if (ret == -1) { return -1; } break; case SMB_ACL_GROUP_OBJ: case SMB_ACL_GROUP: /* Ignore all group entries. */ break; case SMB_ACL_MASK: ret = map_acl_perms_to_permset(mmode, &permset); if (ret == -1) { return -1; } got_mask = true; break; case SMB_ACL_OTHER: ret = map_acl_perms_to_permset(omode, &permset); if (ret == -1) { return -1; } break; default: errno = EINVAL; return -1; } ret = sys_acl_set_permset(entry, permset); if (ret == -1) { return -1; } /* Move to next entry. */ entry_id = SMB_ACL_NEXT_ENTRY; } /* * If we didn't see a mask entry, add one. */ if (!got_mask) { SMB_ACL_ENTRY_T mask_entry; SMB_ACL_PERMSET_T mask_permset; ret = sys_acl_create_entry(&the_acl, &mask_entry); if (ret == -1) { return -1; } ret = map_acl_perms_to_permset(mmode, &mask_permset); if (ret == -1) { return -1; } ret = sys_acl_set_permset(mask_entry, mask_permset); if (ret == -1) { return -1; } ret = sys_acl_set_tag_type(mask_entry, SMB_ACL_MASK); if (ret == -1) { return -1; } /* In case we were realloced and moved. */ *pp_the_acl = the_acl; } return 0; } static int fake_acls_chmod(vfs_handle_struct *handle, const struct smb_filename *smb_fname_in, mode_t mode) { TALLOC_CTX *frame = talloc_stackframe(); int ret = -1; SMB_ACL_T the_acl = NULL; struct smb_filename *smb_fname = NULL; NTSTATUS status; status = synthetic_pathref(talloc_tos(), handle->conn->cwd_fsp, smb_fname_in->base_name, NULL, NULL, smb_fname_in->twrp, smb_fname_in->flags, &smb_fname); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(frame); return -1; } /* * Passthrough first to preserve the * S_ISUID | S_ISGID | S_ISVTX * bits. */ ret = SMB_VFS_NEXT_CHMOD(handle, smb_fname, mode); if (ret == -1) { TALLOC_FREE(frame); return -1; } the_acl = fake_acls_sys_acl_get_file(handle, smb_fname, SMB_ACL_TYPE_ACCESS, talloc_tos()); if (the_acl == NULL) { TALLOC_FREE(frame); if (errno == ENOATTR) { /* No ACL on this file. Just passthrough. */ return 0; } return -1; } ret = fake_acl_process_chmod(&the_acl, smb_fname->st.st_ex_uid, mode); if (ret == -1) { TALLOC_FREE(frame); return -1; } ret = fake_acls_sys_acl_set_file(handle, smb_fname, SMB_ACL_TYPE_ACCESS, the_acl); TALLOC_FREE(frame); return ret; } static int fake_acls_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode) { TALLOC_CTX *frame = talloc_stackframe(); int ret = -1; SMB_ACL_T the_acl = NULL; /* * Passthrough first to preserve the * S_ISUID | S_ISGID | S_ISVTX * bits. */ ret = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); if (ret == -1) { TALLOC_FREE(frame); return -1; } the_acl = fake_acls_sys_acl_get_fd(handle, fsp, talloc_tos()); if (the_acl == NULL) { TALLOC_FREE(frame); if (errno == ENOATTR) { /* No ACL on this file. Just passthrough. */ return 0; } return -1; } ret = fake_acl_process_chmod(&the_acl, fsp->fsp_name->st.st_ex_uid, mode); if (ret == -1) { TALLOC_FREE(frame); return -1; } ret = fake_acls_sys_acl_set_fd(handle, fsp, SMB_ACL_TYPE_ACCESS, the_acl); TALLOC_FREE(frame); return ret; } static struct vfs_fn_pointers vfs_fake_acls_fns = { .stat_fn = fake_acls_stat, .lstat_fn = fake_acls_lstat, .fstat_fn = fake_acls_fstat, .chmod_fn = fake_acls_chmod, .fchmod_fn = fake_acls_fchmod, .sys_acl_get_file_fn = fake_acls_sys_acl_get_file, .sys_acl_get_fd_fn = fake_acls_sys_acl_get_fd, .sys_acl_blob_get_file_fn = posix_sys_acl_blob_get_file, .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd, .sys_acl_set_fd_fn = fake_acls_sys_acl_set_fd, .sys_acl_delete_def_file_fn = fake_acls_sys_acl_delete_def_file, .lchown_fn = fake_acls_lchown, .fchown_fn = fake_acls_fchown, }; static_decl_vfs; NTSTATUS vfs_fake_acls_init(TALLOC_CTX *ctx) { return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fake_acls", &vfs_fake_acls_fns); }