/* Unix SMB/CIFS implementation. Wrap VxFS calls in vfs functions. This module is for ACL handling. Copyright (C) Symantec Corporation 2014 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 "librpc/gen_ndr/ndr_xattr.h" #include "../libcli/security/security.h" #include "../librpc/gen_ndr/ndr_security.h" #include "system/filesys.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_VFS #define MODULE_NAME "vxfs" /* * WARNING !! WARNING !! * * DO NOT CHANGE THIS FROM "system." space to * "user." space unless you are shipping a product * that RESTRICTS access to extended attributes * to smbd-only. "system." space is restricted * to root access only, "user." space is available * to ANY USER. * * If this is changed to "user." and access * to extended attributes is available via * local processes or other remote file system * (e.g. NFS) then the security of the system * WILL BE COMPROMISED. i.e. non-root users * WILL be able to overwrite Samba ACLs on * the file system. * * If you need to modify this define, do * so using CFLAGS on your build command * line. * e.g. CFLAGS=-DXATTR_USER_NTACL="user.NTACL" * * Added by: 17 Sept. 2014. * */ #ifndef XATTR_USER_NTACL #define XATTR_USER_NTACL "system.NTACL" #endif /* type values */ #define VXFS_ACL_UNDEFINED_TYPE 0 #define VXFS_ACL_USER_OBJ 1 #define VXFS_ACL_GROUP_OBJ 2 #define VXFS_ACL_USER 3 #define VXFS_ACL_GROUP 4 #define VXFS_ACL_OTHER 5 #define VXFS_ACL_MASK 6 /* * Compare aces * This will compare two ace entries for sorting * each entry contains: type, perms and id * Sort by type first, if type is same sort by id. */ static int vxfs_ace_cmp(const void *ace1, const void *ace2) { int ret = 0; uint16_t type_a1, type_a2; uint32_t id_a1, id_a2; /* Type must be compared first */ type_a1 = SVAL(ace1, 0); type_a2 = SVAL(ace2, 0); ret = (type_a1 - type_a2); if (!ret) { /* Compare ID under type */ /* skip perm thus take offset as 4*/ id_a1 = IVAL(ace1, 4); id_a2 = IVAL(ace2, 4); ret = id_a1 - id_a2; } return ret; } static void vxfs_print_ace_buf(char *buf, int count) { int i, offset = 0; uint16_t type, perm; uint32_t id; DEBUG(10, ("vfs_vxfs: Printing aces:\n")); for (i = 0; i < count; i++) { type = SVAL(buf, offset); offset += 2; perm = SVAL(buf, offset); offset += 2; id = IVAL(buf, offset); offset += 4; DEBUG(10, ("vfs_vxfs: type = %u, perm = %u, id = %u\n", (unsigned int)type, (unsigned int)perm, (unsigned int)id)); } } /* * Sort aces so that comparing 2 ACLs will be straight forward. * This function will fill buffer as follows: * For each ace: * 1. ace->a_type will be filled as first 2 bytes in buf. * 2. ace->a_perm will be filled as next 2 bytes. * 3. ace->xid will be filled as next 4 bytes. * Thus each ace entry in buf is equal to 8 bytes. * Also a_type is mapped to VXFS_ACL_* so that ordering aces * becomes easy. */ static char * vxfs_sort_acl(SMB_ACL_T theacl, TALLOC_CTX *mem_ctx, uint32_t o_uid, uint32_t o_gid) { struct smb_acl_entry *smb_ace; int i, count; uint16_t type, perm; uint32_t id; int offset = 0; char *buf = NULL; count = theacl->count; buf = talloc_zero_size(mem_ctx, count * 8); if (!buf) { return NULL; } smb_ace = theacl->acl; for (i = 0; i < count; i++) { /* Calculate type */ /* Map type to SMB_ACL_* to VXFS_ACL_* */ switch(smb_ace->a_type) { case SMB_ACL_USER: type = VXFS_ACL_USER; break; case SMB_ACL_USER_OBJ: type = VXFS_ACL_USER_OBJ; break; case SMB_ACL_GROUP: type = VXFS_ACL_GROUP; break; case SMB_ACL_GROUP_OBJ: type = VXFS_ACL_GROUP_OBJ; break; case SMB_ACL_OTHER: type = VXFS_ACL_OTHER; break; case SMB_ACL_MASK: type = VXFS_ACL_MASK; break; default: type = -1; talloc_free(buf); return NULL; } type = type & 0xff; /* Calculate id: * We get owner uid and owner group gid in o_uid and o_gid * Put these ids instead of -1 */ switch(smb_ace->a_type) { case SMB_ACL_USER: id = smb_ace->info.user.uid; break; case SMB_ACL_GROUP: id = smb_ace->info.group.gid; break; case SMB_ACL_USER_OBJ: id = o_uid; break; case SMB_ACL_GROUP_OBJ: id = o_gid; break; case SMB_ACL_MASK: case SMB_ACL_OTHER: id = -1; break; default: /* Can't happen.. */ id = -1; break; } /* Calculate perm */ perm = smb_ace->a_perm & 0xff; /* TYPE is the first 2 bytes of an entry */ SSVAL(buf, offset, type); offset += 2; /* PERM is the next 2 bytes of an entry */ SSVAL(buf, offset, perm); offset += 2; /* ID is the last 4 bytes of an entry */ SIVAL(buf, offset, id); offset += 4; smb_ace++; } qsort(buf, count, 8, vxfs_ace_cmp); DEBUG(10, ("vfs_vxfs: Print sorted aces:\n")); vxfs_print_ace_buf(buf, count); return buf; } /* This function gets e_buf as an arg which is sorted and created out of * existing ACL. This function will compact this e_buf to c_buf where USER * and GROUP aces matching with USER_OBJ and GROUP_OBJ will be merged * respectively. * This is similar to what posix_acls.c does. This will make sure existing * acls are converted much similar to what posix_acls calculates. */ static char * vxfs_compact_buf(char *e_buf, int *new_count, int count, TALLOC_CTX *mem_ctx) { int i, e_offset = 0, c_offset = 0; uint16_t type, perm, o_perm; uint32_t id, owner_id, group_id; char *c_buf = NULL; if (count < 2) { return NULL; } c_buf = talloc_zero_size(mem_ctx, count * 8); if (!c_buf) { return NULL; } /*Copy first two enries from e_buf to c_buf *These are USER_OBJ and GROUP_OBJ */ memcpy(c_buf, e_buf, 16); (*new_count) = 2; owner_id = IVAL(e_buf, 4); group_id = IVAL(e_buf, 12); c_offset = e_offset = 16; /* Start comparing other entries */ for (i = 2; i < count; i++) { type = SVAL(e_buf, e_offset); e_offset += 2; perm = SVAL(e_buf, e_offset); e_offset += 2; id = IVAL(e_buf, e_offset); e_offset += 4; switch(type) { case VXFS_ACL_USER: if (id == owner_id) { o_perm = SVAL(c_buf, 2); o_perm |= perm; SSVAL(c_buf, 2, o_perm); DEBUG(10, ("vfs_vxfs: merging with owner" "e_type = %u," "e_perm = %u," "e_id = %u\n", (unsigned int)type, (unsigned int)perm, (unsigned int)id)); continue; } break; case VXFS_ACL_GROUP: if (id == group_id) { o_perm = SVAL(c_buf, 10); o_perm |= perm; SSVAL(c_buf, 10, o_perm); DEBUG(10, ("vfs_vxfs: merging with owner group" "e_type = %u," "e_perm = %u," "e_id = %u\n", (unsigned int)type, (unsigned int)perm, (unsigned int)id)); continue; } break; } SSVAL(c_buf, c_offset, type); c_offset += 2; SSVAL(c_buf, c_offset, perm); c_offset += 2; SIVAL(c_buf, c_offset, id); c_offset += 4; (*new_count)++; } DEBUG(10, ("vfs_vxfs: new_count is %d\n", *new_count)); return c_buf; } /* Actually compare New ACL and existing ACL buf */ static bool vxfs_compare_acls(char *e_buf, char *n_buf, int n_count, int e_count) { uint16_t e_type, n_type, e_perm, n_perm; uint32_t e_id, n_id; int i, offset = 0; if (!e_buf && !n_buf) { DEBUG(10, ("vfs_vxfs: Empty buffers!\n")); return false; } if ((e_count < 2) || (n_count < 2)) { return false; } /*Get type from last entry from both buffers. * It may or may not be ACL_MASK */ n_type = SVAL(n_buf, offset + (8 * (n_count-1))); e_type = SVAL(e_buf, offset + (8 * (e_count-1))); /* Check for ACL_MASK entry properly. Handle all 4 cases*/ /* If ACL_MASK entry is present in any of the buffers, * it will be always the last one. Calculate count to compare * based on if ACL_MASK is present on new and existing ACL */ if ((n_type != VXFS_ACL_MASK) && (e_type == VXFS_ACL_MASK)){ DEBUG(10, ("vfs_vxfs: New ACL does not have mask entry," "reduce count by 1 and compare\n")); e_count = e_count -1; } if ((n_type == VXFS_ACL_MASK) && (e_type != VXFS_ACL_MASK)){ DEBUG(10, ("vfs_vxfs: new ACL to be set contains mask" "existing ACL does not have mask entry\n" "Need to set New ACL\n")); return false; } if (memcmp(e_buf, n_buf, (e_count * 8)) != 0) { DEBUG(10, ("vfs_vxfs: Compare with memcmp," "buffers not same!\n")); return false; } return true; } /* In VxFS, POSIX ACLs are pointed by separate inode for each file/dir. * However, files/dir share same POSIX ACL inode if ACLs are inherited * from parent. * To retain this behaviour, below function avoids ACL set call if * underlying ACLs are already same and thus saves creating extra inode. * * This function will execute following steps: * 1. Get existing ACL * 2. Sort New ACL and existing ACL into buffers * 3. Compact existing ACL buf * 4. Finally compare New ACL buf and Compact buf * 5. If same, return true * 6. Else need to set New ACL */ static bool vxfs_compare(connection_struct *conn, char *name, SMB_ACL_T the_acl, SMB_ACL_TYPE_T the_acl_type) { SMB_ACL_T existing_acl = NULL; bool ret = false; int i, count = 0; TALLOC_CTX *mem_ctx = talloc_tos(); char *existing_buf = NULL, *new_buf = NULL, *compact_buf = NULL; struct smb_filename *smb_fname = NULL; int status; DEBUG(10, ("vfs_vxfs: Getting existing ACL for %s\n", name)); existing_acl = SMB_VFS_SYS_ACL_GET_FILE(conn, name, the_acl_type, mem_ctx); if (existing_acl == NULL) { DEBUG(10, ("vfs_vxfs: Failed to get ACL\n")); goto out; } DEBUG(10, ("vfs_vxfs: Existing ACL count=%d\n", existing_acl->count)); DEBUG(10, ("vfs_vxfs: New ACL count=%d\n", the_acl->count)); if (existing_acl->count == 0) { DEBUG(10, ("vfs_vxfs: ACL count is 0, Need to set\n")); goto out; } smb_fname = synthetic_smb_fname(mem_ctx, name, NULL, NULL); if (smb_fname == NULL) { DEBUG(10, ("vfs_vxfs: Failed to create smb_fname\n")); goto out; } status = SMB_VFS_STAT(conn, smb_fname); if (status == -1) { DEBUG(10, ("vfs_vxfs: stat failed!\n")); goto out; } DEBUG(10, ("vfs_vxfs: Sorting existing ACL\n")); existing_buf = vxfs_sort_acl(existing_acl, mem_ctx, smb_fname->st.st_ex_uid, smb_fname->st.st_ex_gid); if (!existing_buf) goto out; DEBUG(10, ("vfs_vxfs: Sorting new ACL\n")); new_buf = vxfs_sort_acl(the_acl, mem_ctx, smb_fname->st.st_ex_uid, smb_fname->st.st_ex_gid); if (!new_buf) { goto out; } DEBUG(10, ("vfs_vxfs: Compact existing buf\n")); compact_buf = vxfs_compact_buf(existing_buf, &count, existing_acl->count, mem_ctx); if (!compact_buf) { goto out; } vxfs_print_ace_buf(compact_buf, count); /* COmpare ACLs only if count is same or mismatch by 1 */ if ((count == the_acl->count) || (count == the_acl->count + 1) || (count+1 == the_acl->count)) { if (vxfs_compare_acls(compact_buf, new_buf, the_acl->count, count)) { DEBUG(10, ("vfs_vxfs: ACLs matched. Not setting.\n")); ret = true; goto out; } else DEBUG(10, ("vfs_vxfs: ACLs NOT matched. Setting\n")); } else { DEBUG(10, ("vfs_vxfs: ACLs count does not match. Setting\n")); } out: TALLOC_FREE(existing_acl); TALLOC_FREE(smb_fname); TALLOC_FREE(existing_buf); TALLOC_FREE(compact_buf); TALLOC_FREE(new_buf); return ret; } static int vxfs_sys_acl_set_fd(vfs_handle_struct *handle, files_struct *fsp, SMB_ACL_T theacl) { if (vxfs_compare(fsp->conn, fsp->fsp_name->base_name, theacl, SMB_ACL_TYPE_ACCESS)) { return 0; } return SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, theacl); } static int vxfs_sys_acl_set_file(vfs_handle_struct *handle, const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) { if (vxfs_compare(handle->conn, (char *)name, theacl, acltype)) { return 0; } return SMB_VFS_NEXT_SYS_ACL_SET_FILE(handle, name, acltype, theacl); } static int vxfs_set_xattr(struct vfs_handle_struct *handle, const char *path, const char *name, const void *value, size_t size, int flags){ DEBUG(10, ("In vxfs_set_xattr\n")); if (strcmp(name, XATTR_NTACL_NAME) == 0) { return SMB_VFS_NEXT_SETXATTR(handle, path, XATTR_USER_NTACL, value, size, flags); } /* Clients can't set XATTR_USER_NTACL directly. */ if (strcasecmp(name, XATTR_USER_NTACL) == 0) { errno = EACCES; return -1; } return SMB_VFS_NEXT_SETXATTR(handle, path, name, value, size, flags); } static int vxfs_fset_xattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name, const void *value, size_t size, int flags){ DEBUG(10, ("In vxfs_fset_xattr\n")); if (strcmp(name, XATTR_NTACL_NAME) == 0) { return SMB_VFS_NEXT_FSETXATTR(handle, fsp, XATTR_USER_NTACL, value, size, flags); } /* Clients can't set XATTR_USER_NTACL directly. */ if (strcasecmp(name, XATTR_USER_NTACL) == 0) { errno = EACCES; return -1; } return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags); } static ssize_t vxfs_get_xattr(struct vfs_handle_struct *handle, const char *path, const char *name, void *value, size_t size){ DEBUG(10, ("In vxfs_get_xattr\n")); if (strcmp(name, XATTR_NTACL_NAME) == 0) { return SMB_VFS_NEXT_GETXATTR(handle, path, XATTR_USER_NTACL, value, size); } /* Clients can't see XATTR_USER_NTACL directly. */ if (strcasecmp(name, XATTR_USER_NTACL) == 0) { errno = ENOATTR; return -1; } return SMB_VFS_NEXT_GETXATTR(handle, path, name, value, size); } static ssize_t vxfs_fget_xattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name, void *value, size_t size){ DEBUG(10, ("In vxfs_fget_xattr\n")); if (strcmp(name, XATTR_NTACL_NAME) == 0) { return SMB_VFS_NEXT_FGETXATTR(handle, fsp, XATTR_USER_NTACL, value, size); } /* Clients can't see XATTR_USER_NTACL directly. */ if (strcasecmp(name, XATTR_USER_NTACL) == 0) { errno = ENOATTR; return -1; } return SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size); } static int vxfs_remove_xattr(struct vfs_handle_struct *handle, const char *path, const char *name){ DEBUG(10, ("In vxfs_remove_xattr\n")); if (strcmp(name, XATTR_NTACL_NAME) == 0) { return SMB_VFS_NEXT_REMOVEXATTR(handle, path, XATTR_USER_NTACL); } /* Clients can't see XATTR_USER_NTACL directly. */ if (strcasecmp(name, XATTR_USER_NTACL) == 0) { errno = ENOATTR; return -1; } return SMB_VFS_NEXT_REMOVEXATTR(handle, path, name); } static int vxfs_fremove_xattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name){ DEBUG(10, ("In vxfs_fremove_xattr\n")); if (strcmp(name, XATTR_NTACL_NAME) == 0) { return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, XATTR_USER_NTACL); } /* Clients can't remove XATTR_USER_NTACL directly. */ if (strcasecmp(name, XATTR_USER_NTACL) == 0) { errno = ENOATTR; return -1; } return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name); } static size_t vxfs_filter_list(char *list, size_t size) { char *str = list; while (str - list < size) { size_t element_len = strlen(str) + 1; if (strcasecmp(str, XATTR_USER_NTACL) == 0) { memmove(str, str + element_len, size - (str - list) - element_len); size -= element_len; continue; } str += element_len; } return size; } static ssize_t vxfs_listxattr(vfs_handle_struct *handle, const char *path, char *list, size_t size) { ssize_t result; result = SMB_VFS_NEXT_LISTXATTR(handle, path, list, size); if (result <= 0) { return result; } /* Remove any XATTR_USER_NTACL elements from the returned list. */ result = vxfs_filter_list(list, result); return result; } static ssize_t vxfs_flistxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, char *list, size_t size) { ssize_t result; result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size); if (result <= 0) { return result; } /* Remove any XATTR_USER_NTACL elements from the returned list. */ result = vxfs_filter_list(list, result); return result; } static int vfs_vxfs_connect(struct vfs_handle_struct *handle, const char *service, const char *user) { int ret = SMB_VFS_NEXT_CONNECT(handle, service, user); if (ret < 0) { return ret; } return 0; } static struct vfs_fn_pointers vfs_vxfs_fns = { .connect_fn = vfs_vxfs_connect, .sys_acl_set_file_fn = vxfs_sys_acl_set_file, .sys_acl_set_fd_fn = vxfs_sys_acl_set_fd, .getxattr_fn = vxfs_get_xattr, .fgetxattr_fn = vxfs_fget_xattr, .listxattr_fn = vxfs_listxattr, .flistxattr_fn = vxfs_flistxattr, .removexattr_fn = vxfs_remove_xattr, .fremovexattr_fn = vxfs_fremove_xattr, .setxattr_fn = vxfs_set_xattr, .fsetxattr_fn = vxfs_fset_xattr, }; NTSTATUS vfs_vxfs_init(void); NTSTATUS vfs_vxfs_init(void) { return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "vxfs", &vfs_vxfs_fns); }