diff --git a/api/acl.go b/api/acl.go index e0b532b..1bd7040 100644 --- a/api/acl.go +++ b/api/acl.go @@ -61,7 +61,9 @@ const ( ) var ( - procSetEntriesInAclW = advapi32.MustFindProc("SetEntriesInAclW") + procSetEntriesInAclW = advapi32.MustFindProc("SetEntriesInAclW") + procGetEffectiveRightsFromAclW = advapi32.MustFindProc("GetEffectiveRightsFromAclW") + procGetExplicitEntriesFromAclW = advapi32.MustFindProc("GetExplicitEntriesFromAclW") ) // https://msdn.microsoft.com/en-us/library/windows/desktop/aa379636.aspx @@ -94,3 +96,63 @@ func SetEntriesInAcl(entries []ExplicitAccess, oldAcl windows.Handle, newAcl *wi } return nil } + +func GetEffectiveRightsFromAcl(oldAcl windows.Handle, sid *windows.SID) (uint32, error) { + trustee := Trustee{ + TrusteeForm: TRUSTEE_IS_SID, + Name: (*uint16)(unsafe.Pointer(sid)), + } + + var rights uint32 + + ret, _, err := procGetEffectiveRightsFromAclW.Call( + uintptr(oldAcl), + uintptr(unsafe.Pointer(&trustee)), + uintptr(unsafe.Pointer(&rights)), + ) + + if ret != 0 { + return 0, err + } + return rights, nil +} + +func GetExplicitEntriesFromAcl(oldAcl windows.Handle) ([]ExplicitAccess, error) { + var ( + count uint32 + list uintptr + ) + + /* TODO: seems like I ought to be able to something like this: + var entries *[]ExplicitAccess + ret, _, err := procGetExplicitEntriesFromAclW.Call( + ..., + uintptr(unsafe.Pointer(&entries)), + ) + but I couldn't figure out how to make it work. I tried a whole + bunch of different combinations but I only ever managed to get an empty list + */ + ret, _, err := procGetExplicitEntriesFromAclW.Call( + uintptr(oldAcl), + uintptr(unsafe.Pointer(&count)), + uintptr(unsafe.Pointer(&list)), + ) + + if ret != 0 { + return []ExplicitAccess{}, err + } + + defer windows.LocalFree(windows.Handle(unsafe.Pointer(list))) + + explicitAccessSize := unsafe.Sizeof(ExplicitAccess{}) + getEntryAtOffset := func(list uintptr, offset uint32) ExplicitAccess { + return *(*ExplicitAccess)(unsafe.Pointer(list + explicitAccessSize*uintptr(offset))) + } + + output := make([]ExplicitAccess, count) + for i := uint32(0); i < count; i++ { + output[i] = getEntryAtOffset(list, i) + } + + return output, nil +} diff --git a/chmod.go b/chmod.go index 0d7c6c6..d80f669 100644 --- a/chmod.go +++ b/chmod.go @@ -11,15 +11,15 @@ import ( // file's group, and everyone else to be explicitly controlled. func Chmod(name string, fileMode os.FileMode) error { // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems - creatorOwnerSID, err := windows.StringToSid("S-1-3-0") + creatorOwnerSID, err := windows.StringToSid(SID_NAME_CREATOR_OWNER) if err != nil { return err } - creatorGroupSID, err := windows.StringToSid("S-1-3-1") + creatorGroupSID, err := windows.StringToSid(SID_NAME_CREATOR_GROUP) if err != nil { return err } - everyoneSID, err := windows.StringToSid("S-1-1-0") + everyoneSID, err := windows.StringToSid(SID_NAME_EVERYONE) if err != nil { return err } @@ -29,8 +29,9 @@ func Chmod(name string, fileMode os.FileMode) error { name, true, false, - GrantSid(((mode&0700)<<23)|((mode&0200)<<9), creatorOwnerSID), - GrantSid(((mode&0070)<<26)|((mode&0020)<<12), creatorGroupSID), - GrantSid(((mode&0007)<<29)|((mode&0002)<<15), everyoneSID), + // explicitly granting SYNCHRONIZE allows later detection of when a 0 mode was specified + GrantSid(((mode&0700)<<23)|((mode&0200)<<9)|SYNCHRONIZE, creatorOwnerSID), + GrantSid(((mode&0070)<<26)|((mode&0020)<<12)|SYNCHRONIZE, creatorGroupSID), + GrantSid(((mode&0007)<<29)|((mode&0002)<<15)|SYNCHRONIZE, everyoneSID), ) } diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..5fb50fc --- /dev/null +++ b/constants.go @@ -0,0 +1,49 @@ +package acl + +import ( + "golang.org/x/sys/windows" +) + +const ( + SID_NAME_CREATOR_OWNER = "S-1-3-0" + SID_NAME_CREATOR_GROUP = "S-1-3-1" + SID_NAME_EVERYONE = "S-1-1-0" +) + +// access mask constants from https://docs.microsoft.com/en-us/windows/desktop/wmisdk/file-and-directory-access-rights-constants +// the x/sys/windows package defines some but not all of these constants +const FILE_READ_DATA = windows.FILE_LIST_DIRECTORY // for a directory, the ability to list contents +// the windows package only has this by the "LIST_DIRECTORY" name +const FILE_WRITE_DATA = 0x02 // for a directory, the ability to add a file +const FILE_APPEND_DATA = windows.FILE_APPEND_DATA // for a directory, the ability to add a subdirectory +const FILE_READ_EA = 0x08 +const FILE_WRITE_EA = 0x10 +const FILE_EXECUTE = 0x20 // for a directory, the ability to traverse +const FILE_READ_ATTRIBUTES = 0x80 +const FILE_WRITE_ATTRIBUTES = windows.FILE_WRITE_ATTRIBUTES +const DELETE = 0x10000 +const SYNCHRONIZE = windows.SYNCHRONIZE + +// these correspond to the GENERIC permissions from https://docs.microsoft.com/en-us/windows/desktop/FileIO/file-security-and-access-rights +// except that PERM_WRITE has DELETE added to it because otherwise it would be impossible to delete or rename a file. + +const PERM_READ uint32 = 0 | + FILE_READ_ATTRIBUTES | + FILE_READ_DATA | + FILE_READ_EA | + windows.STANDARD_RIGHTS_READ | + SYNCHRONIZE + +const PERM_WRITE uint32 = 0 | + FILE_APPEND_DATA | + FILE_WRITE_ATTRIBUTES | + FILE_WRITE_DATA | + FILE_WRITE_EA | + windows.STANDARD_RIGHTS_WRITE | + SYNCHRONIZE + +const PERM_EXECUTE uint32 = 0 | + FILE_EXECUTE | + FILE_READ_ATTRIBUTES | + windows.STANDARD_RIGHTS_EXECUTE | + SYNCHRONIZE diff --git a/getaccessmode.go b/getaccessmode.go new file mode 100644 index 0000000..884578d --- /dev/null +++ b/getaccessmode.go @@ -0,0 +1,167 @@ +package acl + +import ( + "os" + "path/filepath" + "unsafe" + + "github.com/hectane/go-acl/api" + "golang.org/x/sys/windows" +) + +func getEffectiveRightsForSid(oldAcl windows.Handle, sid *windows.SID) (uint32, error) { + return api.GetEffectiveRightsFromAcl(oldAcl, sid) +} + +func getEffectiveRightsForSidName(oldAcl windows.Handle, sidName string) (uint32, error) { + sid, err := windows.StringToSid(sidName) + if err != nil { + return 0, err + } + + return getEffectiveRightsForSid(oldAcl, sid) +} + +func getAccessModeForRights(rights uint32) uint32 { + var ret uint32 + + if rights&PERM_READ == PERM_READ { + ret |= 04 + } + if rights&PERM_WRITE == PERM_WRITE { + ret |= 02 + } + if rights&PERM_EXECUTE == PERM_EXECUTE { + ret |= 01 + } + + return ret +} + +func GetExplicitAccessMode(name string) (os.FileMode, error) { + var ( + oldAcl windows.Handle + secDesc windows.Handle + + owner *windows.SID + group *windows.SID + ) + + path, err := filepath.Abs(name) + if err != nil { + return os.FileMode(0), err + } + + err = api.GetNamedSecurityInfo( + path, + api.SE_FILE_OBJECT, + api.DACL_SECURITY_INFORMATION| + api.OWNER_SECURITY_INFORMATION| + api.GROUP_SECURITY_INFORMATION, + &owner, + &group, + &oldAcl, + nil, + &secDesc, + ) + if err != nil { + return os.FileMode(0), err + } + defer windows.LocalFree(secDesc) + + ownerName, err := owner.String() + if err != nil { + return os.FileMode(0), err + } + + groupName, err := group.String() + if err != nil { + return os.FileMode(0), err + } + + entries, err := api.GetExplicitEntriesFromAcl(oldAcl) + if err != nil { + return os.FileMode(0), err + } + + var mode uint32 + if len(entries) > 0 { + for _, item := range entries { + if item.AccessMode == api.GRANT_ACCESS && item.Trustee.TrusteeForm == api.TRUSTEE_IS_SID { + trustee := (*windows.SID)(unsafe.Pointer(item.Trustee.Name)) + + name, err := trustee.String() + if err != nil { + continue + } + + switch name { + case ownerName: + mode |= (getAccessModeForRights(item.AccessPermissions) << 6) + case groupName: + mode |= (getAccessModeForRights(item.AccessPermissions) << 3) + case SID_NAME_EVERYONE: + mode |= getAccessModeForRights(item.AccessPermissions) + + } + } + } + } + + return os.FileMode(mode), nil +} + +func GetEffectiveAccessMode(name string) (os.FileMode, error) { + // get the file's current ACL + var ( + oldAcl windows.Handle + secDesc windows.Handle + + owner *windows.SID + group *windows.SID + ) + + path, err := filepath.Abs(name) + if err != nil { + return os.FileMode(0), err + } + + err = api.GetNamedSecurityInfo( + path, + api.SE_FILE_OBJECT, + api.DACL_SECURITY_INFORMATION| + api.OWNER_SECURITY_INFORMATION| + api.GROUP_SECURITY_INFORMATION, + &owner, + &group, + &oldAcl, + nil, + &secDesc, + ) + if err != nil { + return os.FileMode(0), err + } + defer windows.LocalFree(secDesc) + + ownerRights, err := getEffectiveRightsForSid(oldAcl, owner) + if err != nil { + return os.FileMode(0), err + } + + groupRights, err := getEffectiveRightsForSid(oldAcl, group) + if err != nil { + return os.FileMode(0), err + } + + everyoneRights, err := getEffectiveRightsForSidName(oldAcl, SID_NAME_EVERYONE) + if err != nil { + return os.FileMode(0), err + } + + mode := os.FileMode( + getAccessModeForRights(ownerRights)<<6 | + getAccessModeForRights(groupRights)<<3 | + getAccessModeForRights(everyoneRights)<<0) + + return mode, nil +}