diff options
Diffstat (limited to 'source3/smbd/posix_acls.c')
-rw-r--r-- | source3/smbd/posix_acls.c | 2313 |
1 files changed, 2313 insertions, 0 deletions
diff --git a/source3/smbd/posix_acls.c b/source3/smbd/posix_acls.c new file mode 100644 index 0000000000..9c8835214f --- /dev/null +++ b/source3/smbd/posix_acls.c @@ -0,0 +1,2313 @@ +/* + Unix SMB/CIFS implementation. + SMB NT Security Descriptor / Unix permission conversion. + Copyright (C) Jeremy Allison 1994-2000 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +/**************************************************************************** + Data structures representing the internal ACE format. +****************************************************************************/ + +enum ace_owner {UID_ACE, GID_ACE, WORLD_ACE}; +enum ace_attribute {ALLOW_ACE, DENY_ACE}; /* Used for incoming NT ACLS. */ + +typedef union posix_id { + uid_t uid; + gid_t gid; + int world; +} posix_id; + +typedef struct canon_ace { + struct canon_ace *next, *prev; + SMB_ACL_TAG_T type; + mode_t perms; /* Only use S_I(R|W|X)USR mode bits here. */ + DOM_SID trustee; + enum ace_owner owner_type; + enum ace_attribute attr; + posix_id unix_ug; +} canon_ace; + +#define ALL_ACE_PERMS (S_IRUSR|S_IWUSR|S_IXUSR) + +/**************************************************************************** + Functions to manipulate the internal ACE format. +****************************************************************************/ + +/**************************************************************************** + Count a linked list of canonical ACE entries. +****************************************************************************/ + +static size_t count_canon_ace_list( canon_ace *list_head ) +{ + size_t count = 0; + canon_ace *ace; + + for (ace = list_head; ace; ace = ace->next) + count++; + + return count; +} + +/**************************************************************************** + Free a linked list of canonical ACE entries. +****************************************************************************/ + +static void free_canon_ace_list( canon_ace *list_head ) +{ + while (list_head) { + canon_ace *old_head = list_head; + DLIST_REMOVE(list_head, list_head); + SAFE_FREE(old_head); + } +} + +/**************************************************************************** + Function to duplicate a canon_ace entry. +****************************************************************************/ + +static canon_ace *dup_canon_ace( canon_ace *src_ace) +{ + canon_ace *dst_ace = (canon_ace *)malloc(sizeof(canon_ace)); + + if (dst_ace == NULL) + return NULL; + + *dst_ace = *src_ace; + dst_ace->prev = dst_ace->next = NULL; + return dst_ace; +} + +/**************************************************************************** + Print out a canon ace. +****************************************************************************/ + +static void print_canon_ace(canon_ace *pace, int num) +{ + fstring str; + + dbgtext( "canon_ace index %d. Type = %s ", num, pace->attr == ALLOW_ACE ? "allow" : "deny" ); + dbgtext( "SID = %s ", sid_to_string( str, &pace->trustee)); + if (pace->owner_type == UID_ACE) { + char *u_name = uidtoname(pace->unix_ug.uid); + dbgtext( "uid %u (%s) ", (unsigned int)pace->unix_ug.uid, u_name); + } else if (pace->owner_type == GID_ACE) { + char *g_name = gidtoname(pace->unix_ug.gid); + dbgtext( "gid %u (%s) ", (unsigned int)pace->unix_ug.gid, g_name); + } else + dbgtext( "other "); + switch (pace->type) { + case SMB_ACL_USER: + dbgtext( "SMB_ACL_USER "); + break; + case SMB_ACL_USER_OBJ: + dbgtext( "SMB_ACL_USER_OBJ "); + break; + case SMB_ACL_GROUP: + dbgtext( "SMB_ACL_GROUP "); + break; + case SMB_ACL_GROUP_OBJ: + dbgtext( "SMB_ACL_GROUP_OBJ "); + break; + case SMB_ACL_OTHER: + dbgtext( "SMB_ACL_OTHER "); + break; + } + dbgtext( "perms "); + dbgtext( "%c", pace->perms & S_IRUSR ? 'r' : '-'); + dbgtext( "%c", pace->perms & S_IWUSR ? 'w' : '-'); + dbgtext( "%c\n", pace->perms & S_IXUSR ? 'x' : '-'); +} + +/**************************************************************************** + Print out a canon ace list. +****************************************************************************/ + +static void print_canon_ace_list(const char *name, canon_ace *ace_list) +{ + int count = 0; + + if( DEBUGLVL( 10 )) { + dbgtext( "print_canon_ace_list: %s\n", name ); + for (;ace_list; ace_list = ace_list->next, count++) + print_canon_ace(ace_list, count ); + } +} + +/**************************************************************************** + Map POSIX ACL perms to canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits). +****************************************************************************/ + +static mode_t convert_permset_to_mode_t(connection_struct *conn, SMB_ACL_PERMSET_T permset) +{ + mode_t ret = 0; + + ret |= (conn->vfs_ops.sys_acl_get_perm(conn, permset, SMB_ACL_READ) ? S_IRUSR : 0); + ret |= (conn->vfs_ops.sys_acl_get_perm(conn, permset, SMB_ACL_WRITE) ? S_IWUSR : 0); + ret |= (conn->vfs_ops.sys_acl_get_perm(conn, permset, SMB_ACL_EXECUTE) ? S_IXUSR : 0); + + return ret; +} + +/**************************************************************************** + Map generic UNIX permissions to canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits). +****************************************************************************/ + +static mode_t unix_perms_to_acl_perms(mode_t mode, int r_mask, int w_mask, int x_mask) +{ + mode_t ret = 0; + + if (mode & r_mask) + ret |= S_IRUSR; + if (mode & w_mask) + ret |= S_IWUSR; + if (mode & x_mask) + ret |= S_IXUSR; + + return ret; +} + +/**************************************************************************** + Map canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits) to + an SMB_ACL_PERMSET_T. +****************************************************************************/ + +static int map_acl_perms_to_permset(connection_struct *conn, mode_t mode, SMB_ACL_PERMSET_T *p_permset) +{ + if (conn->vfs_ops.sys_acl_clear_perms(conn, *p_permset) == -1) + return -1; + if (mode & S_IRUSR) { + if (conn->vfs_ops.sys_acl_add_perm(conn, *p_permset, SMB_ACL_READ) == -1) + return -1; + } + if (mode & S_IWUSR) { + if (conn->vfs_ops.sys_acl_add_perm(conn, *p_permset, SMB_ACL_WRITE) == -1) + return -1; + } + if (mode & S_IXUSR) { + if (conn->vfs_ops.sys_acl_add_perm(conn, *p_permset, SMB_ACL_EXECUTE) == -1) + return -1; + } + return 0; +} +/**************************************************************************** + Function to create owner and group SIDs from a SMB_STRUCT_STAT. +****************************************************************************/ + +static void create_file_sids(SMB_STRUCT_STAT *psbuf, DOM_SID *powner_sid, DOM_SID *pgroup_sid) +{ + uid_to_sid( powner_sid, psbuf->st_uid ); + gid_to_sid( pgroup_sid, psbuf->st_gid ); +} + +/**************************************************************************** + Merge aces with a common sid - if both are allow or deny, OR the permissions together and + delete the second one. If the first is deny, mask the permissions off and delete the allow + if the permissions become zero, delete the deny if the permissions are non zero. +****************************************************************************/ + +static void merge_aces( canon_ace **pp_list_head ) +{ + canon_ace *list_head = *pp_list_head; + canon_ace *curr_ace_outer; + canon_ace *curr_ace_outer_next; + + /* + * First, merge allow entries with identical SIDs, and deny entries + * with identical SIDs. + */ + + for (curr_ace_outer = list_head; curr_ace_outer; curr_ace_outer = curr_ace_outer_next) { + canon_ace *curr_ace; + canon_ace *curr_ace_next; + + curr_ace_outer_next = curr_ace_outer->next; /* Save the link in case we delete. */ + + for (curr_ace = curr_ace_outer->next; curr_ace; curr_ace = curr_ace_next) { + + curr_ace_next = curr_ace->next; /* Save the link in case of delete. */ + + if (sid_equal(&curr_ace->trustee, &curr_ace_outer->trustee) && + (curr_ace->attr == curr_ace_outer->attr)) { + + if( DEBUGLVL( 10 )) { + dbgtext("merge_aces: Merging ACE's\n"); + print_canon_ace( curr_ace_outer, 0); + print_canon_ace( curr_ace, 0); + } + + /* Merge two allow or two deny ACE's. */ + + curr_ace_outer->perms |= curr_ace->perms; + DLIST_REMOVE(list_head, curr_ace); + SAFE_FREE(curr_ace); + curr_ace_outer_next = curr_ace_outer->next; /* We may have deleted the link. */ + } + } + } + + /* + * Now go through and mask off allow permissions with deny permissions. + * We can delete either the allow or deny here as we know that each SID + * appears only once in the list. + */ + + for (curr_ace_outer = list_head; curr_ace_outer; curr_ace_outer = curr_ace_outer_next) { + canon_ace *curr_ace; + canon_ace *curr_ace_next; + + curr_ace_outer_next = curr_ace_outer->next; /* Save the link in case we delete. */ + + for (curr_ace = curr_ace_outer->next; curr_ace; curr_ace = curr_ace_next) { + + curr_ace_next = curr_ace->next; /* Save the link in case of delete. */ + + /* + * Subtract ACE's with different entries. Due to the ordering constraints + * we've put on the ACL, we know the deny must be the first one. + */ + + if (sid_equal(&curr_ace->trustee, &curr_ace_outer->trustee) && + (curr_ace_outer->attr == DENY_ACE) && (curr_ace->attr == ALLOW_ACE)) { + + if( DEBUGLVL( 10 )) { + dbgtext("merge_aces: Masking ACE's\n"); + print_canon_ace( curr_ace_outer, 0); + print_canon_ace( curr_ace, 0); + } + + curr_ace->perms &= ~curr_ace_outer->perms; + + if (curr_ace->perms == 0) { + + /* + * The deny overrides the allow. Remove the allow. + */ + + DLIST_REMOVE(list_head, curr_ace); + SAFE_FREE(curr_ace); + curr_ace_outer_next = curr_ace_outer->next; /* We may have deleted the link. */ + + } else { + + /* + * Even after removing permissions, there + * are still allow permissions - delete the deny. + * It is safe to delete the deny here, + * as we are guarenteed by the deny first + * ordering that all the deny entries for + * this SID have already been merged into one + * before we can get to an allow ace. + */ + + DLIST_REMOVE(list_head, curr_ace_outer); + SAFE_FREE(curr_ace_outer); + break; + } + } + + } /* end for curr_ace */ + } /* end for curr_ace_outer */ + + /* We may have modified the list. */ + + *pp_list_head = list_head; +} + +/**************************************************************************** + Map canon_ace perms to permission bits NT. + The attr element is not used here - we only process deny entries on set, + not get. Deny entries are implicit on get with ace->perms = 0. +****************************************************************************/ + +static SEC_ACCESS map_canon_ace_perms(int *pacl_type, DOM_SID *powner_sid, canon_ace *ace) +{ + SEC_ACCESS sa; + uint32 nt_mask = 0; + + *pacl_type = SEC_ACE_TYPE_ACCESS_ALLOWED; + + if ((ace->perms & ALL_ACE_PERMS) == ALL_ACE_PERMS) { + nt_mask = UNIX_ACCESS_RWX; + } else if ((ace->perms & ALL_ACE_PERMS) == (mode_t)0) { + nt_mask = UNIX_ACCESS_NONE; + } else { + nt_mask |= ((ace->perms & S_IRUSR) ? UNIX_ACCESS_R : 0 ); + nt_mask |= ((ace->perms & S_IWUSR) ? UNIX_ACCESS_W : 0 ); + nt_mask |= ((ace->perms & S_IXUSR) ? UNIX_ACCESS_X : 0 ); + } + + DEBUG(10,("map_canon_ace_perms: Mapped (UNIX) %x to (NT) %x\n", + (unsigned int)ace->perms, (unsigned int)nt_mask )); + + init_sec_access(&sa,nt_mask); + return sa; +} + +/**************************************************************************** + Map NT perms to a UNIX mode_t. +****************************************************************************/ + +#define FILE_SPECIFIC_READ_BITS (FILE_READ_DATA|FILE_READ_EA|FILE_READ_ATTRIBUTES) +#define FILE_SPECIFIC_WRITE_BITS (FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_EA|FILE_WRITE_ATTRIBUTES) +#define FILE_SPECIFIC_EXECUTE_BITS (FILE_EXECUTE) + +static mode_t map_nt_perms( SEC_ACCESS sec_access, int type) +{ + mode_t mode = 0; + + switch(type) { + case S_IRUSR: + if(sec_access.mask & GENERIC_ALL_ACCESS) + mode = S_IRUSR|S_IWUSR|S_IXUSR; + else { + mode |= (sec_access.mask & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRUSR : 0; + mode |= (sec_access.mask & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWUSR : 0; + mode |= (sec_access.mask & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXUSR : 0; + } + break; + case S_IRGRP: + if(sec_access.mask & GENERIC_ALL_ACCESS) + mode = S_IRGRP|S_IWGRP|S_IXGRP; + else { + mode |= (sec_access.mask & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRGRP : 0; + mode |= (sec_access.mask & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWGRP : 0; + mode |= (sec_access.mask & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXGRP : 0; + } + break; + case S_IROTH: + if(sec_access.mask & GENERIC_ALL_ACCESS) + mode = S_IROTH|S_IWOTH|S_IXOTH; + else { + mode |= (sec_access.mask & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IROTH : 0; + mode |= (sec_access.mask & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWOTH : 0; + mode |= (sec_access.mask & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXOTH : 0; + } + break; + } + + return mode; +} + +/**************************************************************************** + Unpack a SEC_DESC into a UNIX owner and group. +****************************************************************************/ + +static BOOL unpack_nt_owners(SMB_STRUCT_STAT *psbuf, uid_t *puser, gid_t *pgrp, uint32 security_info_sent, SEC_DESC *psd) +{ + DOM_SID owner_sid; + DOM_SID grp_sid; + enum SID_NAME_USE sid_type; + + *puser = (uid_t)-1; + *pgrp = (gid_t)-1; + + if(security_info_sent == 0) { + DEBUG(0,("unpack_nt_owners: no security info sent !\n")); + return True; + } + + /* + * Validate the owner and group SID's. + */ + + memset(&owner_sid, '\0', sizeof(owner_sid)); + memset(&grp_sid, '\0', sizeof(grp_sid)); + + DEBUG(5,("unpack_nt_owners: validating owner_sids.\n")); + + /* + * Don't immediately fail if the owner sid cannot be validated. + * This may be a group chown only set. + */ + + if (security_info_sent & OWNER_SECURITY_INFORMATION) { + sid_copy(&owner_sid, psd->owner_sid); + if (!sid_to_uid( &owner_sid, puser, &sid_type)) { + DEBUG(3,("unpack_nt_owners: unable to validate owner sid for %s\n", + sid_string_static(&owner_sid))); + return False; + } + } + + /* + * Don't immediately fail if the group sid cannot be validated. + * This may be an owner chown only set. + */ + + if (security_info_sent & GROUP_SECURITY_INFORMATION) { + sid_copy(&grp_sid, psd->grp_sid); + if (!sid_to_gid( &grp_sid, pgrp, &sid_type)) { + DEBUG(3,("unpack_nt_owners: unable to validate group sid.\n")); + return False; + } + } + + DEBUG(5,("unpack_nt_owners: owner_sids validated.\n")); + + return True; +} + +/**************************************************************************** + Ensure the enforced permissions for this share apply. +****************************************************************************/ + +static mode_t apply_default_perms(files_struct *fsp, mode_t perms, mode_t type) +{ + int snum = SNUM(fsp->conn); + mode_t and_bits = (mode_t)0; + mode_t or_bits = (mode_t)0; + + /* Get the initial bits to apply. */ + + if (fsp->is_directory) { + and_bits = lp_dir_security_mask(snum); + or_bits = lp_force_dir_security_mode(snum); + } else { + and_bits = lp_security_mask(snum); + or_bits = lp_force_security_mode(snum); + } + + /* Now bounce them into the S_USR space. */ + switch(type) { + case S_IRUSR: + and_bits = unix_perms_to_acl_perms(and_bits, S_IRUSR, S_IWUSR, S_IXUSR); + or_bits = unix_perms_to_acl_perms(or_bits, S_IRUSR, S_IWUSR, S_IXUSR); + break; + case S_IRGRP: + and_bits = unix_perms_to_acl_perms(and_bits, S_IRGRP, S_IWGRP, S_IXGRP); + or_bits = unix_perms_to_acl_perms(or_bits, S_IRGRP, S_IWGRP, S_IXGRP); + break; + case S_IROTH: + and_bits = unix_perms_to_acl_perms(and_bits, S_IROTH, S_IWOTH, S_IXOTH); + or_bits = unix_perms_to_acl_perms(or_bits, S_IROTH, S_IWOTH, S_IXOTH); + break; + } + + return ((perms & and_bits)|or_bits); +} + +/**************************************************************************** + A well formed POSIX file or default ACL has at least 3 entries, a + SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ. + In addition, the owner must always have at least read access. + When using this call on get_acl, the pst struct is valid and contains + the mode of the file. When using this call on set_acl, the pst struct has + been modified to have a mode containing the default for this file or directory + type. +****************************************************************************/ + +static BOOL ensure_canon_entry_valid(canon_ace **pp_ace, + files_struct *fsp, + DOM_SID *pfile_owner_sid, + DOM_SID *pfile_grp_sid, + SMB_STRUCT_STAT *pst, + BOOL setting_acl) +{ + extern DOM_SID global_sid_World; + canon_ace *pace; + BOOL got_user = False; + BOOL got_grp = False; + BOOL got_other = False; + + for (pace = *pp_ace; pace; pace = pace->next) { + if (pace->type == SMB_ACL_USER_OBJ) { + + if (setting_acl) { + /* Ensure owner has read access. */ + pace->perms |= S_IRUSR; + if (fsp->is_directory) + pace->perms |= (S_IWUSR|S_IXUSR); + + /* + * Ensure create mask/force create mode is respected on set. + */ + + pace->perms = apply_default_perms(fsp, pace->perms, S_IRUSR); + } + + got_user = True; + } else if (pace->type == SMB_ACL_GROUP_OBJ) { + + /* + * Ensure create mask/force create mode is respected on set. + */ + + if (setting_acl) + pace->perms = apply_default_perms(fsp, pace->perms, S_IRGRP); + got_grp = True; + } else if (pace->type == SMB_ACL_OTHER) { + + /* + * Ensure create mask/force create mode is respected on set. + */ + + if (setting_acl) + pace->perms = apply_default_perms(fsp, pace->perms, S_IROTH); + got_other = True; + } + } + + if (!got_user) { + if ((pace = (canon_ace *)malloc(sizeof(canon_ace))) == NULL) { + DEBUG(0,("ensure_canon_entry_valid: malloc fail.\n")); + return False; + } + + ZERO_STRUCTP(pace); + pace->type = SMB_ACL_USER_OBJ; + pace->owner_type = UID_ACE; + pace->unix_ug.uid = pst->st_uid; + pace->trustee = *pfile_owner_sid; + pace->perms = unix_perms_to_acl_perms(pst->st_mode, S_IRUSR, S_IWUSR, S_IXUSR); + pace->attr = ALLOW_ACE; + + DLIST_ADD(*pp_ace, pace); + } + + if (!got_grp) { + if ((pace = (canon_ace *)malloc(sizeof(canon_ace))) == NULL) { + DEBUG(0,("ensure_canon_entry_valid: malloc fail.\n")); + return False; + } + + ZERO_STRUCTP(pace); + pace->type = SMB_ACL_GROUP_OBJ; + pace->owner_type = GID_ACE; + pace->unix_ug.uid = pst->st_gid; + pace->trustee = *pfile_grp_sid; + pace->perms = unix_perms_to_acl_perms(pst->st_mode, S_IRGRP, S_IWGRP, S_IXGRP); + pace->attr = ALLOW_ACE; + + DLIST_ADD(*pp_ace, pace); + } + + if (!got_other) { + if ((pace = (canon_ace *)malloc(sizeof(canon_ace))) == NULL) { + DEBUG(0,("ensure_canon_entry_valid: malloc fail.\n")); + return False; + } + + ZERO_STRUCTP(pace); + pace->type = SMB_ACL_OTHER; + pace->owner_type = WORLD_ACE; + pace->unix_ug.world = -1; + pace->trustee = global_sid_World; + pace->perms = unix_perms_to_acl_perms(pst->st_mode, S_IROTH, S_IWOTH, S_IXOTH); + pace->attr = ALLOW_ACE; + + DLIST_ADD(*pp_ace, pace); + } + + return True; +} + +/**************************************************************************** + Unpack a SEC_DESC into two canonical ace lists. +****************************************************************************/ + +static BOOL create_canon_ace_lists(files_struct *fsp, + DOM_SID *pfile_owner_sid, + DOM_SID *pfile_grp_sid, + canon_ace **ppfile_ace, canon_ace **ppdir_ace, + SEC_ACL *dacl) +{ + extern DOM_SID global_sid_World; + extern struct generic_mapping file_generic_mapping; + BOOL all_aces_are_inherit_only = (fsp->is_directory ? True : False); + canon_ace *file_ace = NULL; + canon_ace *dir_ace = NULL; + canon_ace *tmp_ace = NULL; + canon_ace *current_ace = NULL; + BOOL got_dir_allow = False; + BOOL got_file_allow = False; + int i, j; + + *ppfile_ace = NULL; + *ppdir_ace = NULL; + + /* + * Convert the incoming ACL into a more regular form. + */ + + for(i = 0; i < dacl->num_aces; i++) { + SEC_ACE *psa = &dacl->ace[i]; + + if((psa->type != SEC_ACE_TYPE_ACCESS_ALLOWED) && (psa->type != SEC_ACE_TYPE_ACCESS_DENIED)) { + DEBUG(3,("create_canon_ace_lists: unable to set anything but an ALLOW or DENY ACE.\n")); + return False; + } + + /* + * The security mask may be UNIX_ACCESS_NONE which should map into + * no permissions (we overload the WRITE_OWNER bit for this) or it + * should be one of the ALL/EXECUTE/READ/WRITE bits. Arrange for this + * to be so. Any other bits override the UNIX_ACCESS_NONE bit. + */ + + /* + * Convert GENERIC bits to specific bits. + */ + + se_map_generic(&psa->info.mask, &file_generic_mapping); + + psa->info.mask &= (UNIX_ACCESS_NONE|FILE_ALL_ACCESS); + + if(psa->info.mask != UNIX_ACCESS_NONE) + psa->info.mask &= ~UNIX_ACCESS_NONE; + } + + /* + * Deal with the fact that NT 4.x re-writes the canonical format + * that we return for default ACLs. If a directory ACE is identical + * to a inherited directory ACE then NT changes the bits so that the + * first ACE is set to OI|IO and the second ACE for this SID is set + * to CI. We need to repair this. JRA. + */ + + for(i = 0; i < dacl->num_aces; i++) { + SEC_ACE *psa1 = &dacl->ace[i]; + + for (j = i + 1; j < dacl->num_aces; j++) { + SEC_ACE *psa2 = &dacl->ace[j]; + + if (psa1->info.mask != psa2->info.mask) + continue; + + if (!sid_equal(&psa1->trustee, &psa2->trustee)) + continue; + + /* + * Ok - permission bits and SIDs are equal. + * Check if flags were re-written. + */ + + if (psa1->flags & SEC_ACE_FLAG_INHERIT_ONLY) { + + psa1->flags |= (psa2->flags & (SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT)); + psa2->flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT); + + } else if (psa2->flags & SEC_ACE_FLAG_INHERIT_ONLY) { + + psa2->flags |= (psa1->flags & (SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT)); + psa1->flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT); + + } + } + } + + for(i = 0; i < dacl->num_aces; i++) { + enum SID_NAME_USE sid_type; + SEC_ACE *psa = &dacl->ace[i]; + + /* + * Ignore non-mappable SIDs (NT Authority, BUILTIN etc). + */ + + if (non_mappable_sid(&psa->trustee)) { + fstring str; + DEBUG(10,("create_canon_ace_lists: ignoring non-mappable SID %s\n", + sid_to_string(str, &psa->trustee) )); + continue; + } + + /* + * Create a cannon_ace entry representing this NT DACL ACE. + */ + + if ((current_ace = (canon_ace *)malloc(sizeof(canon_ace))) == NULL) { + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + DEBUG(0,("create_canon_ace_lists: malloc fail.\n")); + return False; + } + + ZERO_STRUCTP(current_ace); + + sid_copy(¤t_ace->trustee, &psa->trustee); + + /* + * Try and work out if the SID is a user or group + * as we need to flag these differently for POSIX. + */ + + if( sid_equal(¤t_ace->trustee, &global_sid_World)) { + current_ace->owner_type = WORLD_ACE; + current_ace->unix_ug.world = -1; + } else if (sid_to_uid( ¤t_ace->trustee, ¤t_ace->unix_ug.uid, &sid_type)) { + current_ace->owner_type = UID_ACE; + } else if (sid_to_gid( ¤t_ace->trustee, ¤t_ace->unix_ug.gid, &sid_type)) { + current_ace->owner_type = GID_ACE; + } else { + fstring str; + + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + DEBUG(0,("create_canon_ace_lists: unable to map SID %s to uid or gid.\n", + sid_to_string(str, ¤t_ace->trustee) )); + SAFE_FREE(current_ace); + return False; + } + + /* + * Map the given NT permissions into a UNIX mode_t containing only + * S_I(R|W|X)USR bits. + */ + + current_ace->perms |= map_nt_perms( psa->info, S_IRUSR); + current_ace->attr = (psa->type == SEC_ACE_TYPE_ACCESS_ALLOWED) ? ALLOW_ACE : DENY_ACE; + + /* + * Now note what kind of a POSIX ACL this should map to. + */ + + if(sid_equal(¤t_ace->trustee, pfile_owner_sid)) { + + current_ace->type = SMB_ACL_USER_OBJ; + + } else if( sid_equal(¤t_ace->trustee, pfile_grp_sid)) { + + current_ace->type = SMB_ACL_GROUP_OBJ; + + } else if( sid_equal(¤t_ace->trustee, &global_sid_World)) { + + current_ace->type = SMB_ACL_OTHER; + + } else { + /* + * Could be a SMB_ACL_USER or SMB_ACL_GROUP. Check by + * looking at owner_type. + */ + + current_ace->type = (current_ace->owner_type == UID_ACE) ? SMB_ACL_USER : SMB_ACL_GROUP; + } + + /* + * Now add the created ace to either the file list, the directory + * list, or both. We *MUST* preserve the order here (hence we use + * DLIST_ADD_END) as NT ACLs are order dependent. + */ + + if (fsp->is_directory) { + + /* + * We can only add to the default POSIX ACE list if the ACE is + * designed to be inherited by both files and directories. + */ + + if ((psa->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT)) == + (SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT)) { + + DLIST_ADD_END(dir_ace, current_ace, tmp_ace); + + /* + * Note if this was an allow ace. We can't process + * any further deny ace's after this. + */ + + if (current_ace->attr == ALLOW_ACE) + got_dir_allow = True; + + if ((current_ace->attr == DENY_ACE) && got_dir_allow) { + DEBUG(0,("create_canon_ace_lists: malformed ACL in inheritable ACL ! \ +Deny entry after Allow entry. Failing to set on file %s.\n", fsp->fsp_name )); + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + SAFE_FREE(current_ace); + return False; + } + + if( DEBUGLVL( 10 )) { + dbgtext("create_canon_ace_lists: adding dir ACL:\n"); + print_canon_ace( current_ace, 0); + } + + /* + * If this is not an inherit only ACE we need to add a duplicate + * to the file acl. + */ + + if (!(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY)) { + canon_ace *dup_ace = dup_canon_ace(current_ace); + + if (!dup_ace) { + DEBUG(0,("create_canon_ace_lists: malloc fail !\n")); + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + return False; + } + + current_ace = dup_ace; + } else { + current_ace = NULL; + } + } + } + + /* + * Only add to the file ACL if not inherit only. + */ + + if (!(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY)) { + DLIST_ADD_END(file_ace, current_ace, tmp_ace); + + /* + * Note if this was an allow ace. We can't process + * any further deny ace's after this. + */ + + if (current_ace->attr == ALLOW_ACE) + got_file_allow = True; + + if ((current_ace->attr == DENY_ACE) && got_file_allow) { + DEBUG(0,("create_canon_ace_lists: malformed ACL in file ACL ! \ +Deny entry after Allow entry. Failing to set on file %s.\n", fsp->fsp_name )); + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + SAFE_FREE(current_ace); + return False; + } + + if( DEBUGLVL( 10 )) { + dbgtext("create_canon_ace_lists: adding file ACL:\n"); + print_canon_ace( current_ace, 0); + } + all_aces_are_inherit_only = False; + current_ace = NULL; + } + + /* + * Free if ACE was not added. + */ + + SAFE_FREE(current_ace); + } + + if (fsp->is_directory && all_aces_are_inherit_only) { + /* + * Windows 2000 is doing one of these weird 'inherit acl' + * traverses to conserve NTFS ACL resources. Just pretend + * there was no DACL sent. JRA. + */ + + DEBUG(10,("create_canon_ace_lists: Win2k inherit acl traverse. Ignoring DACL.\n")); + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + file_ace = NULL; + dir_ace = NULL; + } + + *ppfile_ace = file_ace; + *ppdir_ace = dir_ace; + + return True; +} + +/**************************************************************************** + Check if a given uid/SID is in a group gid/SID. This is probably very + expensive and will need optimisation. A *lot* of optimisation :-). JRA. +****************************************************************************/ + +static BOOL uid_entry_in_group( canon_ace *uid_ace, canon_ace *group_ace ) +{ + extern DOM_SID global_sid_World; + fstring u_name; + fstring g_name; + + /* "Everyone" always matches every uid. */ + + if (sid_equal(&group_ace->trustee, &global_sid_World)) + return True; + + fstrcpy(u_name, uidtoname(uid_ace->unix_ug.uid)); + fstrcpy(g_name, gidtoname(group_ace->unix_ug.gid)); + + /* + * Due to the winbind interfaces we need to do this via names, + * not uids/gids. + */ + + return user_in_group_list(u_name, g_name ); +} + +/**************************************************************************** + ASCII art time again... JRA :-). + + We have 3 cases to process when moving from an NT ACL to a POSIX ACL. Firstly, + we insist the ACL is in canonical form (ie. all DENY entries preceede ALLOW + entries). Secondly, the merge code has ensured that all duplicate SID entries for + allow or deny have been merged, so the same SID can only appear once in the deny + list or once in the allow list. + + We then process as follows : + + --------------------------------------------------------------------------- + First pass - look for a Everyone DENY entry. + + If it is deny all (rwx) trunate the list at this point. + Else, walk the list from this point and use the deny permissions of this + entry as a mask on all following allow entries. Finally, delete + the Everyone DENY entry (we have applied it to everything possible). + + In addition, in this pass we remove any DENY entries that have + no permissions (ie. they are a DENY nothing). + --------------------------------------------------------------------------- + Second pass - only deal with deny user entries. + + DENY user1 (perms XXX) + + new_perms = 0 + for all following allow group entries where user1 is in group + new_perms |= group_perms; + + user1 entry perms = new_perms & ~ XXX; + + Convert the deny entry to an allow entry with the new perms and + push to the end of the list. Note if the user was in no groups + this maps to a specific allow nothing entry for this user. + + The common case from the NT ACL choser (userX deny all) is + optimised so we don't do the group lookup - we just map to + an allow nothing entry. + + What we're doing here is inferring the allow permissions the + person setting the ACE on user1 wanted by looking at the allow + permissions on the groups the user is currently in. This will + be a snapshot, depending on group membership but is the best + we can do and has the advantage of failing closed rather than + open. + --------------------------------------------------------------------------- + Third pass - only deal with deny group entries. + + DENY group1 (perms XXX) + + for all following allow user entries where user is in group1 + user entry perms = user entry perms & ~ XXX; + + If there is a group Everyone allow entry with permissions YYY, + convert the group1 entry to an allow entry and modify its + permissions to be : + + new_perms = YYY & ~ XXX + + and push to the end of the list. + + If there is no group Everyone allow entry then convert the + group1 entry to a allow nothing entry and push to the end of the list. + + Note that the common case from the NT ACL choser (groupX deny all) + cannot be optimised here as we need to modify user entries who are + in the group to change them to a deny all also. + + What we're doing here is modifying the allow permissions of + user entries (which are more specific in POSIX ACLs) to mask + out the explicit deny set on the group they are in. This will + be a snapshot depending on current group membership but is the + best we can do and has the advantage of failing closed rather + than open. + --------------------------------------------------------------------------- + + Note we *MUST* do the deny user pass first as this will convert deny user + entries into allow user entries which can then be processed by the deny + group pass. + + The above algorithm took a *lot* of thinking about - hence this + explaination :-). JRA. +****************************************************************************/ + +/**************************************************************************** + Process a canon_ace list entries. This is very complex code. We need + to go through and remove the "deny" permissions from any allow entry that matches + the id of this entry. We have already refused any NT ACL that wasn't in correct + order (DENY followed by ALLOW). If any allow entry ends up with zero permissions, + we just remove it (to fail safe). We have already removed any duplicate ace + entries. Treat an "Everyone" DENY_ACE as a special case - use it to mask all + allow entries. +****************************************************************************/ + +static void process_deny_list( canon_ace **pp_ace_list ) +{ + extern DOM_SID global_sid_World; + canon_ace *ace_list = *pp_ace_list; + canon_ace *curr_ace = NULL; + canon_ace *curr_ace_next = NULL; + + /* Pass 1 above - look for an Everyone, deny entry. */ + + for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) { + canon_ace *allow_ace_p; + + curr_ace_next = curr_ace->next; /* So we can't lose the link. */ + + if (curr_ace->attr != DENY_ACE) + continue; + + if (curr_ace->perms == (mode_t)0) { + + /* Deny nothing entry - delete. */ + + DLIST_REMOVE(ace_list, curr_ace); + continue; + } + + if (!sid_equal(&curr_ace->trustee, &global_sid_World)) + continue; + + /* JRATEST - assert. */ + SMB_ASSERT(curr_ace->owner_type == WORLD_ACE); + + if (curr_ace->perms == ALL_ACE_PERMS) { + + /* + * Optimisation. This is a DENY_ALL to Everyone. Truncate the + * list at this point including this entry. + */ + + canon_ace *prev_entry = curr_ace->prev; + + free_canon_ace_list( curr_ace ); + if (prev_entry) + prev_entry->next = NULL; + else { + /* We deleted the entire list. */ + ace_list = NULL; + } + break; + } + + for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) { + + /* + * Only mask off allow entries. + */ + + if (allow_ace_p->attr != ALLOW_ACE) + continue; + + allow_ace_p->perms &= ~curr_ace->perms; + } + + /* + * Now it's been applied, remove it. + */ + + DLIST_REMOVE(ace_list, curr_ace); + } + + /* Pass 2 above - deal with deny user entries. */ + + for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) { + mode_t new_perms = (mode_t)0; + canon_ace *allow_ace_p; + canon_ace *tmp_ace; + + curr_ace_next = curr_ace->next; /* So we can't lose the link. */ + + if (curr_ace->attr != DENY_ACE) + continue; + + if (curr_ace->owner_type != UID_ACE) + continue; + + if (curr_ace->perms == ALL_ACE_PERMS) { + + /* + * Optimisation - this is a deny everything to this user. + * Convert to an allow nothing and push to the end of the list. + */ + + curr_ace->attr = ALLOW_ACE; + curr_ace->perms = (mode_t)0; + DLIST_DEMOTE(ace_list, curr_ace, tmp_ace); + continue; + } + + for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) { + + if (allow_ace_p->attr != ALLOW_ACE) + continue; + + /* We process GID_ACE and WORLD_ACE entries only. */ + + if (allow_ace_p->owner_type == UID_ACE) + continue; + + if (uid_entry_in_group( curr_ace, allow_ace_p)) + new_perms |= allow_ace_p->perms; + } + + /* + * Convert to a allow entry, modify the perms and push to the end + * of the list. + */ + + curr_ace->attr = ALLOW_ACE; + curr_ace->perms = (new_perms & ~curr_ace->perms); + DLIST_DEMOTE(ace_list, curr_ace, tmp_ace); + } + + /* Pass 3 above - deal with deny group entries. */ + + for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) { + canon_ace *tmp_ace; + canon_ace *allow_ace_p; + canon_ace *allow_everyone_p = NULL; + + curr_ace_next = curr_ace->next; /* So we can't lose the link. */ + + if (curr_ace->attr != DENY_ACE) + continue; + + if (curr_ace->owner_type != GID_ACE) + continue; + + for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) { + + if (allow_ace_p->attr != ALLOW_ACE) + continue; + + /* Store a pointer to the Everyone allow, if it exists. */ + if (allow_ace_p->owner_type == WORLD_ACE) + allow_everyone_p = allow_ace_p; + + /* We process UID_ACE entries only. */ + + if (allow_ace_p->owner_type != UID_ACE) + continue; + + /* Mask off the deny group perms. */ + + if (uid_entry_in_group( allow_ace_p, curr_ace)) + allow_ace_p->perms &= ~curr_ace->perms; + } + + /* + * Convert the deny to an allow with the correct perms and + * push to the end of the list. + */ + + curr_ace->attr = ALLOW_ACE; + if (allow_everyone_p) + curr_ace->perms = allow_everyone_p->perms & ~curr_ace->perms; + else + curr_ace->perms = (mode_t)0; + DLIST_DEMOTE(ace_list, curr_ace, tmp_ace); + + } + + *pp_ace_list = ace_list; +} + +/**************************************************************************** + Create a default mode that will be used if a security descriptor entry has + no user/group/world entries. +****************************************************************************/ + +static mode_t create_default_mode(files_struct *fsp, BOOL interitable_mode) +{ + int snum = SNUM(fsp->conn); + mode_t and_bits = (mode_t)0; + mode_t or_bits = (mode_t)0; + mode_t mode = interitable_mode ? unix_mode( fsp->conn, FILE_ATTRIBUTE_ARCHIVE, fsp->fsp_name) : S_IRUSR; + + if (fsp->is_directory) + mode |= (S_IWUSR|S_IXUSR); + + /* + * Now AND with the create mode/directory mode bits then OR with the + * force create mode/force directory mode bits. + */ + + if (fsp->is_directory) { + and_bits = lp_dir_security_mask(snum); + or_bits = lp_force_dir_security_mode(snum); + } else { + and_bits = lp_security_mask(snum); + or_bits = lp_force_security_mode(snum); + } + + return ((mode & and_bits)|or_bits); +} + +/**************************************************************************** + Unpack a SEC_DESC into two canonical ace lists. We don't depend on this + succeeding. +****************************************************************************/ + +static BOOL unpack_canon_ace(files_struct *fsp, + SMB_STRUCT_STAT *pst, + DOM_SID *pfile_owner_sid, + DOM_SID *pfile_grp_sid, + canon_ace **ppfile_ace, canon_ace **ppdir_ace, + uint32 security_info_sent, SEC_DESC *psd) +{ + canon_ace *file_ace = NULL; + canon_ace *dir_ace = NULL; + + *ppfile_ace = NULL; + *ppdir_ace = NULL; + + if(security_info_sent == 0) { + DEBUG(0,("unpack_canon_ace: no security info sent !\n")); + return False; + } + + /* + * If no DACL then this is a chown only security descriptor. + */ + + if(!(security_info_sent & DACL_SECURITY_INFORMATION) || !psd->dacl) + return True; + + /* + * Now go through the DACL and create the canon_ace lists. + */ + + if (!create_canon_ace_lists( fsp, pfile_owner_sid, pfile_grp_sid, + &file_ace, &dir_ace, psd->dacl)) + return False; + + if ((file_ace == NULL) && (dir_ace == NULL)) { + /* W2K traverse DACL set - ignore. */ + return True; + } + + /* + * Go through the canon_ace list and merge entries + * belonging to identical users of identical allow or deny type. + * We can do this as all deny entries come first, followed by + * all allow entries (we have mandated this before accepting this acl). + */ + + print_canon_ace_list( "file ace - before merge", file_ace); + merge_aces( &file_ace ); + + print_canon_ace_list( "dir ace - before merge", dir_ace); + merge_aces( &dir_ace ); + + /* + * NT ACLs are order dependent. Go through the acl lists and + * process DENY entries by masking the allow entries. + */ + + print_canon_ace_list( "file ace - before deny", file_ace); + process_deny_list( &file_ace); + + print_canon_ace_list( "dir ace - before deny", dir_ace); + process_deny_list( &dir_ace); + + /* + * A well formed POSIX file or default ACL has at least 3 entries, a + * SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ + * and optionally a mask entry. Ensure this is the case. + */ + + print_canon_ace_list( "file ace - before valid", file_ace); + + /* + * A default 3 element mode entry for a file should be r-- --- ---. + * A default 3 element mode entry for a directory should be rwx --- ---. + */ + + pst->st_mode = create_default_mode(fsp, False); + + if (!ensure_canon_entry_valid(&file_ace, fsp, pfile_owner_sid, pfile_grp_sid, pst, True)) { + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + return False; + } + + print_canon_ace_list( "dir ace - before valid", dir_ace); + + /* + * A default inheritable 3 element mode entry for a directory should be the + * mode Samba will use to create a file within. Ensure user rwx bits are set if + * it's a directory. + */ + + pst->st_mode = create_default_mode(fsp, True); + + if (!ensure_canon_entry_valid(&dir_ace, fsp, pfile_owner_sid, pfile_grp_sid, pst, True)) { + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + return False; + } + + print_canon_ace_list( "file ace - return", file_ace); + print_canon_ace_list( "dir ace - return", dir_ace); + + *ppfile_ace = file_ace; + *ppdir_ace = dir_ace; + return True; + +} + +/****************************************************************************** + When returning permissions, try and fit NT display + semantics if possible. Note the the canon_entries here must have been malloced. + The list format should be - first entry = owner, followed by group and other user + entries, last entry = other. + + Note that this doesn't exactly match the NT semantics for an ACL. As POSIX entries + are not ordered, and match on the most specific entry rather than walking a list, + then a simple POSIX permission of rw-r--r-- should really map to 6 entries, + + Entry 0: owner : deny all except read and write. + Entry 1: group : deny all except read. + Entry 2: Everyone : deny all except read. + Entry 3: owner : allow read and write. + Entry 4: group : allow read. + Entry 5: Everyone : allow read. + + But NT cannot display this in their ACL editor ! +********************************************************************************/ + +static void arrange_posix_perms( char *filename, canon_ace **pp_list_head) +{ + canon_ace *list_head = *pp_list_head; + canon_ace *owner_ace = NULL; + canon_ace *other_ace = NULL; + canon_ace *ace = NULL; + + for (ace = list_head; ace; ace = ace->next) { + if (ace->type == SMB_ACL_USER_OBJ) + owner_ace = ace; + else if (ace->type == SMB_ACL_OTHER) { + /* Last ace - this is "other" */ + other_ace = ace; + } + } + + if (!owner_ace || !other_ace) { + DEBUG(0,("arrange_posix_perms: Invalid POSIX permissions for file %s, missing owner or other.\n", + filename )); + return; + } + + /* + * The POSIX algorithm applies to owner first, and other last, + * so ensure they are arranged in this order. + */ + + if (owner_ace) { + DLIST_PROMOTE(list_head, owner_ace); + } + + if (other_ace) { + DLIST_DEMOTE(list_head, other_ace, ace); + } + + /* We have probably changed the head of the list. */ + + *pp_list_head = list_head; +} + +/**************************************************************************** + Create a linked list of canonical ACE entries. +****************************************************************************/ + +static canon_ace *canonicalise_acl( files_struct *fsp, SMB_ACL_T posix_acl, SMB_STRUCT_STAT *psbuf, + DOM_SID *powner, DOM_SID *pgroup) +{ + extern DOM_SID global_sid_World; + connection_struct *conn = fsp->conn; + mode_t acl_mask = (S_IRUSR|S_IWUSR|S_IXUSR); + canon_ace *list_head = NULL; + canon_ace *ace = NULL; + canon_ace *next_ace = NULL; + int entry_id = SMB_ACL_FIRST_ENTRY; + SMB_ACL_ENTRY_T entry; + size_t ace_count; + + while ( posix_acl && (conn->vfs_ops.sys_acl_get_entry(conn, posix_acl, entry_id, &entry) == 1)) { + SMB_ACL_TAG_T tagtype; + SMB_ACL_PERMSET_T permset; + DOM_SID sid; + posix_id unix_ug; + enum ace_owner owner_type; + + /* get_next... */ + if (entry_id == SMB_ACL_FIRST_ENTRY) + entry_id = SMB_ACL_NEXT_ENTRY; + + /* Is this a MASK entry ? */ + if (conn->vfs_ops.sys_acl_get_tag_type(conn, entry, &tagtype) == -1) + continue; + + if (conn->vfs_ops.sys_acl_get_permset(conn, entry, &permset) == -1) + continue; + + /* Decide which SID to use based on the ACL type. */ + switch(tagtype) { + case SMB_ACL_USER_OBJ: + /* Get the SID from the owner. */ + uid_to_sid( &sid, psbuf->st_uid ); + unix_ug.uid = psbuf->st_uid; + owner_type = UID_ACE; + break; + case SMB_ACL_USER: + { + uid_t *puid = (uid_t *)conn->vfs_ops.sys_acl_get_qualifier(conn, entry); + if (puid == NULL) { + DEBUG(0,("canonicalise_acl: Failed to get uid.\n")); + continue; + } + uid_to_sid( &sid, *puid); + unix_ug.uid = *puid; + owner_type = UID_ACE; + conn->vfs_ops.sys_acl_free_qualifier(conn, (void *)puid,tagtype); + break; + } + case SMB_ACL_GROUP_OBJ: + /* Get the SID from the owning group. */ + gid_to_sid( &sid, psbuf->st_gid ); + unix_ug.gid = psbuf->st_gid; + owner_type = GID_ACE; + break; + case SMB_ACL_GROUP: + { + gid_t *pgid = (gid_t *)conn->vfs_ops.sys_acl_get_qualifier(conn, entry); + if (pgid == NULL) { + DEBUG(0,("canonicalise_acl: Failed to get gid.\n")); + continue; + } + gid_to_sid( &sid, *pgid); + unix_ug.gid = *pgid; + owner_type = GID_ACE; + conn->vfs_ops.sys_acl_free_qualifier(conn, (void *)pgid,tagtype); + break; + } + case SMB_ACL_MASK: + acl_mask = convert_permset_to_mode_t(conn, permset); + continue; /* Don't count the mask as an entry. */ + case SMB_ACL_OTHER: + /* Use the Everyone SID */ + sid = global_sid_World; + unix_ug.world = -1; + owner_type = WORLD_ACE; + break; + default: + DEBUG(0,("canonicalise_acl: Unknown tagtype %u\n", (unsigned int)tagtype)); + continue; + } + + /* + * Add this entry to the list. + */ + + if ((ace = (canon_ace *)malloc(sizeof(canon_ace))) == NULL) + goto fail; + + ZERO_STRUCTP(ace); + ace->type = tagtype; + ace->perms = convert_permset_to_mode_t(conn, permset); + ace->attr = ALLOW_ACE; + ace->trustee = sid; + ace->unix_ug = unix_ug; + ace->owner_type = owner_type; + + DLIST_ADD(list_head, ace); + } + + /* + * This next call will ensure we have at least a user/group/world set. + */ + + if (!ensure_canon_entry_valid(&list_head, fsp, powner, pgroup, psbuf, False)) + goto fail; + + arrange_posix_perms(fsp->fsp_name,&list_head ); + + /* + * Now go through the list, masking the permissions with the + * acl_mask. Ensure all DENY Entries are at the start of the list. + */ + + DEBUG(10,("canonicalise_acl: ace entries before arrange :\n")); + + for ( ace_count = 0, ace = list_head; ace; ace = next_ace, ace_count++) { + next_ace = ace->next; + + /* Masks are only applied to entries other than USER_OBJ and OTHER. */ + if (ace->type != SMB_ACL_OTHER && ace->type != SMB_ACL_USER_OBJ) + ace->perms &= acl_mask; + + if (ace->perms == 0) { + DLIST_PROMOTE(list_head, ace); + } + + if( DEBUGLVL( 10 ) ) { + print_canon_ace(ace, ace_count); + } + } + + print_canon_ace_list( "canonicalise_acl: ace entries after arrange", list_head ); + + return list_head; + + fail: + + free_canon_ace_list(list_head); + return NULL; +} + +/**************************************************************************** + Attempt to apply an ACL to a file or directory. +****************************************************************************/ + +static BOOL set_canon_ace_list(files_struct *fsp, canon_ace *the_ace, BOOL default_ace, BOOL *pacl_set_support) +{ + connection_struct *conn = fsp->conn; + BOOL ret = False; + SMB_ACL_T the_acl = conn->vfs_ops.sys_acl_init(conn, (int)count_canon_ace_list(the_ace) + 1); + canon_ace *p_ace; + int i; + SMB_ACL_ENTRY_T mask_entry; + SMB_ACL_PERMSET_T mask_permset; + SMB_ACL_TYPE_T the_acl_type = (default_ace ? SMB_ACL_TYPE_DEFAULT : SMB_ACL_TYPE_ACCESS); + + if (the_acl == NULL) { + + if (errno != ENOSYS) { + /* + * Only print this error message if we have some kind of ACL + * support that's not working. Otherwise we would always get this. + */ + DEBUG(0,("set_canon_ace_list: Unable to init %s ACL. (%s)\n", + default_ace ? "default" : "file", strerror(errno) )); + } + *pacl_set_support = False; + return False; + } + + for (i = 0, p_ace = the_ace; p_ace; p_ace = p_ace->next, i++ ) { + SMB_ACL_ENTRY_T the_entry; + SMB_ACL_PERMSET_T the_permset; + + /* + * Get the entry for this ACE. + */ + + if (conn->vfs_ops.sys_acl_create_entry(conn, &the_acl, &the_entry) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to create entry %d. (%s)\n", + i, strerror(errno) )); + goto done; + } + + /* + * Ok - we now know the ACL calls should be working, don't + * allow fallback to chmod. + */ + + *pacl_set_support = True; + + /* + * Initialise the entry from the canon_ace. + */ + + /* + * First tell the entry what type of ACE this is. + */ + + if (conn->vfs_ops.sys_acl_set_tag_type(conn, the_entry, p_ace->type) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to set tag type on entry %d. (%s)\n", + i, strerror(errno) )); + goto done; + } + + /* + * Only set the qualifier (user or group id) if the entry is a user + * or group id ACE. + */ + + if ((p_ace->type == SMB_ACL_USER) || (p_ace->type == SMB_ACL_GROUP)) { + if (conn->vfs_ops.sys_acl_set_qualifier(conn, the_entry,(void *)&p_ace->unix_ug.uid) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to set qualifier on entry %d. (%s)\n", + i, strerror(errno) )); + goto done; + } + } + + /* + * Convert the mode_t perms in the canon_ace to a POSIX permset. + */ + + if (conn->vfs_ops.sys_acl_get_permset(conn, the_entry, &the_permset) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to get permset on entry %d. (%s)\n", + i, strerror(errno) )); + goto done; + } + + if (map_acl_perms_to_permset(conn, p_ace->perms, &the_permset) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to create permset for mode (%u) on entry %d. (%s)\n", + (unsigned int)p_ace->perms, i, strerror(errno) )); + goto done; + } + + /* + * ..and apply them to the entry. + */ + + if (conn->vfs_ops.sys_acl_set_permset(conn, the_entry, the_permset) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to add permset on entry %d. (%s)\n", + i, strerror(errno) )); + goto done; + } + + if( DEBUGLVL( 10 )) + print_canon_ace( p_ace, i); + } + + /* + * Add in a mask of rwx. + */ + + if (conn->vfs_ops.sys_acl_create_entry( conn, &the_acl, &mask_entry) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to create mask entry. (%s)\n", strerror(errno) )); + goto done; + } + + if (conn->vfs_ops.sys_acl_set_tag_type(conn, mask_entry, SMB_ACL_MASK) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to set tag type on mask entry. (%s)\n",strerror(errno) )); + goto done; + } + + if (conn->vfs_ops.sys_acl_get_permset(conn, mask_entry, &mask_permset) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to get mask permset. (%s)\n", strerror(errno) )); + goto done; + } + + if (map_acl_perms_to_permset(conn, S_IRUSR|S_IWUSR|S_IXUSR, &mask_permset) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to create mask permset. (%s)\n", strerror(errno) )); + goto done; + } + + if (conn->vfs_ops.sys_acl_set_permset(conn, mask_entry, mask_permset) == -1) { + DEBUG(0,("set_canon_ace_list: Failed to add mask permset. (%s)\n", strerror(errno) )); + goto done; + } + + /* + * Check if the ACL is valid. + */ + + if (conn->vfs_ops.sys_acl_valid(conn, the_acl) == -1) { + DEBUG(0,("set_canon_ace_list: ACL type (%s) is invalid for set (%s).\n", + the_acl_type == SMB_ACL_TYPE_DEFAULT ? "directory default" : "file", + strerror(errno) )); + goto done; + } + + /* + * Finally apply it to the file or directory. + */ + + if(default_ace || fsp->is_directory || fsp->fd == -1) { + if (conn->vfs_ops.sys_acl_set_file(conn, fsp->fsp_name, the_acl_type, the_acl) == -1) { + /* + * Some systems allow all the above calls and only fail with no ACL support + * when attempting to apply the acl. HPUX with HFS is an example of this. JRA. + */ + if (errno == ENOSYS) + *pacl_set_support = False; + DEBUG(2,("set_canon_ace_list: sys_acl_set_file type %s failed for file %s (%s).\n", + the_acl_type == SMB_ACL_TYPE_DEFAULT ? "directory default" : "file", + fsp->fsp_name, strerror(errno) )); + goto done; + } + } else { + if (conn->vfs_ops.sys_acl_set_fd(fsp, fsp->fd, the_acl) == -1) { + /* + * Some systems allow all the above calls and only fail with no ACL support + * when attempting to apply the acl. HPUX with HFS is an example of this. JRA. + */ + if (errno == ENOSYS) + *pacl_set_support = False; + DEBUG(2,("set_canon_ace_list: sys_acl_set_file failed for file %s (%s).\n", + fsp->fsp_name, strerror(errno) )); + goto done; + } + } + + ret = True; + + done: + + if (the_acl != NULL) + conn->vfs_ops.sys_acl_free_acl(conn, the_acl); + + return ret; +} + +/**************************************************************************** + Convert a canon_ace to a generic 3 element permission - if possible. +****************************************************************************/ + +#define MAP_PERM(p,mask,result) (((p) & (mask)) ? (result) : 0 ) + +static BOOL convert_canon_ace_to_posix_perms( files_struct *fsp, canon_ace *file_ace_list, mode_t *posix_perms) +{ + int snum = SNUM(fsp->conn); + size_t ace_count = count_canon_ace_list(file_ace_list); + canon_ace *ace_p; + canon_ace *owner_ace = NULL; + canon_ace *group_ace = NULL; + canon_ace *other_ace = NULL; + mode_t and_bits; + mode_t or_bits; + + if (ace_count != 3) { + DEBUG(3,("convert_canon_ace_to_posix_perms: Too many ACE entries for file %s to convert to \ +posix perms.\n", fsp->fsp_name )); + return False; + } + + for (ace_p = file_ace_list; ace_p; ace_p = ace_p->next) { + if (ace_p->owner_type == UID_ACE) + owner_ace = ace_p; + else if (ace_p->owner_type == GID_ACE) + group_ace = ace_p; + else if (ace_p->owner_type == WORLD_ACE) + other_ace = ace_p; + } + + if (!owner_ace || !group_ace || !other_ace) { + DEBUG(3,("convert_canon_ace_to_posix_perms: Can't get standard entries for file %s.\n", + fsp->fsp_name )); + return False; + } + + *posix_perms = (mode_t)0; + + *posix_perms |= owner_ace->perms; + *posix_perms |= MAP_PERM(group_ace->perms, S_IRUSR, S_IRGRP); + *posix_perms |= MAP_PERM(group_ace->perms, S_IWUSR, S_IWGRP); + *posix_perms |= MAP_PERM(group_ace->perms, S_IXUSR, S_IXGRP); + *posix_perms |= MAP_PERM(other_ace->perms, S_IRUSR, S_IROTH); + *posix_perms |= MAP_PERM(other_ace->perms, S_IWUSR, S_IWOTH); + *posix_perms |= MAP_PERM(other_ace->perms, S_IXUSR, S_IXOTH); + + /* The owner must have at least read access. */ + + *posix_perms |= S_IRUSR; + if (fsp->is_directory) + *posix_perms |= (S_IWUSR|S_IXUSR); + + /* If requested apply the masks. */ + + /* Get the initial bits to apply. */ + + if (fsp->is_directory) { + and_bits = lp_dir_security_mask(snum); + or_bits = lp_force_dir_security_mode(snum); + } else { + and_bits = lp_security_mask(snum); + or_bits = lp_force_security_mode(snum); + } + + *posix_perms = (((*posix_perms) & and_bits)|or_bits); + + DEBUG(10,("convert_canon_ace_to_posix_perms: converted u=%o,g=%o,w=%o to perm=0%o for file %s.\n", + (int)owner_ace->perms, (int)group_ace->perms, (int)other_ace->perms, (int)*posix_perms, + fsp->fsp_name )); + + return True; +} + +static int nt_ace_comp( SEC_ACE *a1, SEC_ACE *a2) +{ + if (a1->type == a2->type) + return 0; + + if (a1->type == SEC_ACE_TYPE_ACCESS_DENIED && a2->type == SEC_ACE_TYPE_ACCESS_ALLOWED) + return -1; + return 1; +} + +/**************************************************************************** + Reply to query a security descriptor from an fsp. If it succeeds it allocates + the space for the return elements and returns the size needed to return the + security descriptor. This should be the only external function needed for + the UNIX style get ACL. +****************************************************************************/ + +size_t get_nt_acl(files_struct *fsp, SEC_DESC **ppdesc) +{ + connection_struct *conn = fsp->conn; + SMB_STRUCT_STAT sbuf; + SEC_ACE *nt_ace_list = NULL; + DOM_SID owner_sid; + DOM_SID group_sid; + size_t sd_size = 0; + SEC_ACL *psa = NULL; + size_t num_acls = 0; + size_t num_dir_acls = 0; + size_t num_aces = 0; + SMB_ACL_T posix_acl = NULL; + SMB_ACL_T dir_acl = NULL; + canon_ace *file_ace = NULL; + canon_ace *dir_ace = NULL; + + *ppdesc = NULL; + + DEBUG(10,("get_nt_acl: called for file %s\n", fsp->fsp_name )); + + if(fsp->is_directory || fsp->fd == -1) { + + /* Get the stat struct for the owner info. */ + if(vfs_stat(fsp->conn,fsp->fsp_name, &sbuf) != 0) { + return 0; + } + /* + * Get the ACL from the path. + */ + + posix_acl = conn->vfs_ops.sys_acl_get_file(conn, fsp->fsp_name, SMB_ACL_TYPE_ACCESS); + + /* + * If it's a directory get the default POSIX ACL. + */ + + if(fsp->is_directory) + dir_acl = conn->vfs_ops.sys_acl_get_file(conn, fsp->fsp_name, SMB_ACL_TYPE_DEFAULT); + + } else { + + /* Get the stat struct for the owner info. */ + if(vfs_fstat(fsp,fsp->fd,&sbuf) != 0) { + return 0; + } + /* + * Get the ACL from the fd. + */ + posix_acl = conn->vfs_ops.sys_acl_get_fd(fsp, fsp->fd); + } + + DEBUG(5,("get_nt_acl : file ACL %s, directory ACL %s\n", + posix_acl ? "present" : "absent", + dir_acl ? "present" : "absent" )); + + /* + * Get the owner, group and world SIDs. + */ + + create_file_sids(&sbuf, &owner_sid, &group_sid); + + /* Create the canon_ace lists. */ + file_ace = canonicalise_acl( fsp, posix_acl, &sbuf, &owner_sid, &group_sid); + num_acls = count_canon_ace_list(file_ace); + + /* We must have *some* ACLS. */ + + if (num_acls == 0) { + DEBUG(0,("get_nt_acl : No ACLs on file (%s) !\n", fsp->fsp_name )); + return 0; + } + + if (fsp->is_directory) { + /* + * If we have to fake a default ACL then this is the mode to use. + */ + sbuf.st_mode = unix_mode( fsp->conn, FILE_ATTRIBUTE_ARCHIVE, fsp->fsp_name); + + dir_ace = canonicalise_acl(fsp, dir_acl, &sbuf, &owner_sid, &group_sid); + num_dir_acls = count_canon_ace_list(dir_ace); + } + + /* Allocate the ace list. */ + if ((nt_ace_list = (SEC_ACE *)malloc((num_acls + num_dir_acls)* sizeof(SEC_ACE))) == NULL) { + DEBUG(0,("get_nt_acl: Unable to malloc space for nt_ace_list.\n")); + goto done; + } + + memset(nt_ace_list, '\0', (num_acls + num_dir_acls) * sizeof(SEC_ACE) ); + + /* + * Create the NT ACE list from the canonical ace lists. + */ + + { + canon_ace *ace; + int nt_acl_type; + int i; + + ace = file_ace; + + for (i = 0; i < num_acls; i++, ace = ace->next) { + SEC_ACCESS acc = map_canon_ace_perms(&nt_acl_type, &owner_sid, ace ); + init_sec_ace(&nt_ace_list[num_aces++], &ace->trustee, nt_acl_type, acc, 0); + } + + ace = dir_ace; + + for (i = 0; i < num_dir_acls; i++, ace = ace->next) { + SEC_ACCESS acc = map_canon_ace_perms(&nt_acl_type, &owner_sid, ace ); + init_sec_ace(&nt_ace_list[num_aces++], &ace->trustee, nt_acl_type, acc, + SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY); + } + + /* + * Sort to force deny entries to the front. + */ + + if (num_acls + num_dir_acls) + qsort( nt_ace_list, num_acls + num_dir_acls, sizeof(nt_ace_list[0]), QSORT_CAST nt_ace_comp); + } + + if (num_acls) { + if((psa = make_sec_acl( main_loop_talloc_get(), ACL_REVISION, num_aces, nt_ace_list)) == NULL) { + DEBUG(0,("get_nt_acl: Unable to malloc space for acl.\n")); + goto done; + } + } + + *ppdesc = make_standard_sec_desc( main_loop_talloc_get(), &owner_sid, &group_sid, psa, &sd_size); + + if(!*ppdesc) { + DEBUG(0,("get_nt_acl: Unable to malloc space for security descriptor.\n")); + sd_size = 0; + } + + done: + + if (posix_acl) + conn->vfs_ops.sys_acl_free_acl(conn, posix_acl); + if (dir_acl) + conn->vfs_ops.sys_acl_free_acl(conn, dir_acl); + free_canon_ace_list(file_ace); + free_canon_ace_list(dir_ace); + SAFE_FREE(nt_ace_list); + + return sd_size; +} + +/**************************************************************************** + Reply to set a security descriptor on an fsp. security_info_sent is the + description of the following NT ACL. + This should be the only external function needed for the UNIX style set ACL. +****************************************************************************/ + +BOOL set_nt_acl(files_struct *fsp, uint32 security_info_sent, SEC_DESC *psd) +{ + connection_struct *conn = fsp->conn; + uid_t user = (uid_t)-1; + gid_t grp = (gid_t)-1; + SMB_STRUCT_STAT sbuf; + DOM_SID file_owner_sid; + DOM_SID file_grp_sid; + canon_ace *file_ace_list = NULL; + canon_ace *dir_ace_list = NULL; + BOOL acl_perms = False; + mode_t orig_mode = (mode_t)0; + uid_t orig_uid; + gid_t orig_gid; + + DEBUG(10,("set_nt_acl: called for file %s\n", fsp->fsp_name )); + + /* + * Get the current state of the file. + */ + + if(fsp->is_directory || fsp->fd == -1) { + if(vfs_stat(fsp->conn,fsp->fsp_name, &sbuf) != 0) + return False; + } else { + if(vfs_fstat(fsp,fsp->fd,&sbuf) != 0) + return False; + } + + /* Save the original elements we check against. */ + orig_mode = sbuf.st_mode; + orig_uid = sbuf.st_uid; + orig_gid = sbuf.st_gid; + + /* + * Unpack the user/group/world id's. + */ + + if (!unpack_nt_owners( &sbuf, &user, &grp, security_info_sent, psd)) + return False; + + /* + * Do we need to chown ? + */ + + if((user != (uid_t)-1 || grp != (uid_t)-1) && (orig_uid != user || orig_gid != grp)) { + + DEBUG(3,("set_nt_acl: chown %s. uid = %u, gid = %u.\n", + fsp->fsp_name, (unsigned int)user, (unsigned int)grp )); + + if(vfs_chown( fsp->conn, fsp->fsp_name, user, grp) == -1) { + DEBUG(3,("set_nt_acl: chown %s, %u, %u failed. Error = %s.\n", + fsp->fsp_name, (unsigned int)user, (unsigned int)grp, strerror(errno) )); + return False; + } + + /* + * Recheck the current state of the file, which may have changed. + * (suid/sgid bits, for instance) + */ + + if(fsp->is_directory) { + if(vfs_stat(fsp->conn, fsp->fsp_name, &sbuf) != 0) { + return False; + } + } else { + + int ret; + + if(fsp->fd == -1) + ret = vfs_stat(fsp->conn, fsp->fsp_name, &sbuf); + else + ret = vfs_fstat(fsp,fsp->fd,&sbuf); + + if(ret != 0) + return False; + } + + /* Save the original elements we check against. */ + orig_mode = sbuf.st_mode; + orig_uid = sbuf.st_uid; + orig_gid = sbuf.st_gid; + } + + create_file_sids(&sbuf, &file_owner_sid, &file_grp_sid); + + acl_perms = unpack_canon_ace( fsp, &sbuf, &file_owner_sid, &file_grp_sid, + &file_ace_list, &dir_ace_list, security_info_sent, psd); + + if ((file_ace_list == NULL) && (dir_ace_list == NULL)) { + /* W2K traverse DACL set - ignore. */ + return True; + } + + if (!acl_perms) { + DEBUG(3,("set_nt_acl: cannot set permissions\n")); + free_canon_ace_list(file_ace_list); + free_canon_ace_list(dir_ace_list); + return False; + } + + /* + * Only change security if we got a DACL. + */ + + if((security_info_sent & DACL_SECURITY_INFORMATION) && (psd->dacl != NULL)) { + + BOOL acl_set_support = False; + BOOL ret = False; + + /* + * Try using the POSIX ACL set first. Fall back to chmod if + * we have no ACL support on this filesystem. + */ + + if (acl_perms && file_ace_list) { + ret = set_canon_ace_list(fsp, file_ace_list, False, &acl_set_support); + if (acl_set_support && ret == False) { + DEBUG(3,("set_nt_acl: failed to set file acl on file %s (%s).\n", fsp->fsp_name, strerror(errno) )); + free_canon_ace_list(file_ace_list); + free_canon_ace_list(dir_ace_list); + return False; + } + } + + if (acl_perms && acl_set_support && fsp->is_directory) { + if (dir_ace_list) { + if (!set_canon_ace_list(fsp, dir_ace_list, True, &acl_set_support)) { + DEBUG(3,("set_nt_acl: failed to set default acl on directory %s (%s).\n", fsp->fsp_name, strerror(errno) )); + free_canon_ace_list(file_ace_list); + free_canon_ace_list(dir_ace_list); + return False; + } + } else { + + /* + * No default ACL - delete one if it exists. + */ + + if (conn->vfs_ops.sys_acl_delete_def_file(conn, fsp->fsp_name) == -1) { + DEBUG(3,("set_nt_acl: sys_acl_delete_def_file failed (%s)\n", strerror(errno))); + free_canon_ace_list(file_ace_list); + return False; + } + } + } + + /* + * If we cannot set using POSIX ACLs we fall back to checking if we need to chmod. + */ + + if(!acl_set_support && acl_perms) { + mode_t posix_perms; + + if (!convert_canon_ace_to_posix_perms( fsp, file_ace_list, &posix_perms)) { + free_canon_ace_list(file_ace_list); + free_canon_ace_list(dir_ace_list); + DEBUG(3,("set_nt_acl: failed to convert file acl to posix permissions for file %s.\n", + fsp->fsp_name )); + return False; + } + + if (orig_mode != posix_perms) { + + DEBUG(3,("set_nt_acl: chmod %s. perms = 0%o.\n", + fsp->fsp_name, (unsigned int)posix_perms )); + + if(conn->vfs_ops.chmod(conn,fsp->fsp_name, posix_perms) == -1) { + DEBUG(3,("set_nt_acl: chmod %s, 0%o failed. Error = %s.\n", + fsp->fsp_name, (unsigned int)posix_perms, strerror(errno) )); + free_canon_ace_list(file_ace_list); + free_canon_ace_list(dir_ace_list); + return False; + } + } + } + } + + free_canon_ace_list(file_ace_list); + free_canon_ace_list(dir_ace_list); + + return True; +} + +/**************************************************************************** + Do a chmod by setting the ACL USER_OBJ, GROUP_OBJ and OTHER bits in an ACL + and set the mask to rwx. Needed to preserve complex ACLs set by NT. +****************************************************************************/ + +static int chmod_acl_internals( connection_struct *conn, SMB_ACL_T posix_acl, mode_t mode) +{ + int entry_id = SMB_ACL_FIRST_ENTRY; + SMB_ACL_ENTRY_T entry; + int num_entries = 0; + + while ( conn->vfs_ops.sys_acl_get_entry(conn, posix_acl, entry_id, &entry) == 1) { + SMB_ACL_TAG_T tagtype; + SMB_ACL_PERMSET_T permset; + mode_t perms; + + /* get_next... */ + if (entry_id == SMB_ACL_FIRST_ENTRY) + entry_id = SMB_ACL_NEXT_ENTRY; + + if (conn->vfs_ops.sys_acl_get_tag_type(conn, entry, &tagtype) == -1) + return -1; + + if (conn->vfs_ops.sys_acl_get_permset(conn, entry, &permset) == -1) + return -1; + + num_entries++; + + switch(tagtype) { + case SMB_ACL_USER_OBJ: + perms = unix_perms_to_acl_perms(mode, S_IRUSR, S_IWUSR, S_IXUSR); + break; + case SMB_ACL_GROUP_OBJ: + perms = unix_perms_to_acl_perms(mode, S_IRGRP, S_IWGRP, S_IXGRP); + break; + case SMB_ACL_MASK: + perms = S_IRUSR|S_IWUSR|S_IXUSR; + break; + case SMB_ACL_OTHER: + perms = unix_perms_to_acl_perms(mode, S_IROTH, S_IWOTH, S_IXOTH); + break; + default: + continue; + } + + if (map_acl_perms_to_permset(conn, perms, &permset) == -1) + return -1; + + if (conn->vfs_ops.sys_acl_set_permset(conn, entry, permset) == -1) + return -1; + } + + /* + * If this is a simple 3 element ACL or no elements then it's a standard + * UNIX permission set. Just use chmod... + */ + + if ((num_entries == 3) || (num_entries == 0)) + return -1; + + return 0; +} + +/**************************************************************************** + Do a chmod by setting the ACL USER_OBJ, GROUP_OBJ and OTHER bits in an ACL + and set the mask to rwx. Needed to preserve complex ACLs set by NT. + Note that name is in UNIX character set. +****************************************************************************/ + +int chmod_acl(connection_struct *conn, const char *name, mode_t mode) +{ + SMB_ACL_T posix_acl = NULL; + int ret = -1; + + if ((posix_acl = conn->vfs_ops.sys_acl_get_file(conn, name, SMB_ACL_TYPE_ACCESS)) == NULL) + return -1; + + if ((ret = chmod_acl_internals(conn, posix_acl, mode)) == -1) + goto done; + + ret = conn->vfs_ops.sys_acl_set_file(conn, name, SMB_ACL_TYPE_ACCESS, posix_acl); + + done: + + conn->vfs_ops.sys_acl_free_acl(conn, posix_acl); + return ret; +} + +/**************************************************************************** + Do an fchmod by setting the ACL USER_OBJ, GROUP_OBJ and OTHER bits in an ACL + and set the mask to rwx. Needed to preserve complex ACLs set by NT. +****************************************************************************/ + +int fchmod_acl(files_struct *fsp, int fd, mode_t mode) +{ + connection_struct *conn = fsp->conn; + SMB_ACL_T posix_acl = NULL; + int ret = -1; + + if ((posix_acl = conn->vfs_ops.sys_acl_get_fd(fsp, fd)) == NULL) + return -1; + + if ((ret = chmod_acl_internals(conn, posix_acl, mode)) == -1) + goto done; + + ret = conn->vfs_ops.sys_acl_set_fd(fsp, fd, posix_acl); + + done: + + conn->vfs_ops.sys_acl_free_acl(conn, posix_acl); + return ret; +} + +BOOL directory_has_default_acl(connection_struct *conn, const char *fname) +{ + SMB_ACL_T dir_acl = conn->vfs_ops.sys_acl_get_file( conn, fname, SMB_ACL_TYPE_DEFAULT); + BOOL has_acl = False; + SMB_ACL_ENTRY_T entry; + + if (dir_acl != NULL && (conn->vfs_ops.sys_acl_get_entry(conn, dir_acl, SMB_ACL_FIRST_ENTRY, &entry) == 1)) + has_acl = True; + + conn->vfs_ops.sys_acl_free_acl(conn, dir_acl); + return has_acl; +} |