| // Copyright 2013 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.enterprise.adaptor.fs; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Sets; |
| import com.google.enterprise.adaptor.fs.WinApi.Netapi32Ex; |
| import com.google.enterprise.adaptor.fs.WinApi.Shlwapi; |
| |
| import com.sun.jna.Memory; |
| import com.sun.jna.Native; |
| import com.sun.jna.Pointer; |
| import com.sun.jna.Structure; |
| import com.sun.jna.WString; |
| import com.sun.jna.platform.win32.Advapi32; |
| import com.sun.jna.platform.win32.Advapi32Util; |
| import com.sun.jna.platform.win32.Advapi32Util.Account; |
| import com.sun.jna.platform.win32.Kernel32; |
| import com.sun.jna.platform.win32.LMErr; |
| import com.sun.jna.platform.win32.W32Errors; |
| import com.sun.jna.platform.win32.Win32Exception; |
| import com.sun.jna.platform.win32.WinError; |
| import com.sun.jna.platform.win32.WinNT; |
| import com.sun.jna.platform.win32.WinNT.SID_NAME_USE; |
| import com.sun.jna.ptr.IntByReference; |
| import com.sun.jna.ptr.PointerByReference; |
| import com.sun.jna.win32.StdCallLibrary; |
| import com.sun.jna.win32.W32APIOptions; |
| |
| import java.io.IOException; |
| import java.nio.file.LinkOption; |
| import java.nio.file.Path; |
| import java.nio.file.attribute.AclEntry; |
| import java.nio.file.attribute.AclEntryFlag; |
| import java.nio.file.attribute.AclEntryPermission; |
| import java.nio.file.attribute.AclEntryType; |
| import java.nio.file.attribute.AclFileAttributeView; |
| import java.nio.file.attribute.GroupPrincipal; |
| import java.nio.file.attribute.UserPrincipal; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Generate various {@link AclFileAttributeView}s for Windows files. |
| */ |
| class WindowsAclFileAttributeViews { |
| |
| private static final Logger log = |
| Logger.getLogger(WindowsAclFileAttributeViews.class.getName()); |
| |
| /** This pattern parses a UNC path to get the host and share details. */ |
| private static final Pattern UNC_PATTERN = |
| Pattern.compile("^\\\\\\\\([^\\\\]+)\\\\([^\\\\]+)"); |
| |
| /** The set of SID_NAME_USE which are groups and not users. */ |
| private static final Set<Integer> GROUP_SID_TYPES = |
| Collections.unmodifiableSet(Sets.newHashSet( |
| SID_NAME_USE.SidTypeAlias, SID_NAME_USE.SidTypeGroup, |
| SID_NAME_USE.SidTypeWellKnownGroup)); |
| |
| /** The set of SID_NAME_USE which are user and not groups. */ |
| private static final Set<Integer> USER_SID_TYPES = |
| Collections.unmodifiableSet(Sets.newHashSet(SID_NAME_USE.SidTypeUser)); |
| |
| /** Map of NT GENERIC permissions to NT FILE permissions. */ |
| private static final Map<Integer, Integer> GENERIC_PERMS_MAP = |
| Collections.unmodifiableMap(new HashMap<Integer, Integer>() { |
| { |
| put(WinNT.GENERIC_READ, WinNT.FILE_GENERIC_READ); |
| put(WinNT.GENERIC_WRITE, WinNT.FILE_GENERIC_WRITE); |
| put(WinNT.GENERIC_EXECUTE, WinNT.FILE_GENERIC_EXECUTE); |
| put(WinNT.GENERIC_ALL, WinNT.FILE_ALL_ACCESS); |
| } |
| }); |
| |
| /** The map of Acl permissions from NT to AclEntryPermission. */ |
| private static final Map<Integer, AclEntryPermission> ACL_PERMS_MAP = |
| Collections.unmodifiableMap(new HashMap<Integer, AclEntryPermission>() { |
| { |
| put(WinNT.FILE_READ_DATA, AclEntryPermission.READ_DATA); |
| put(WinNT.FILE_READ_ATTRIBUTES, |
| AclEntryPermission.READ_ATTRIBUTES); |
| put(WinNT.FILE_READ_EA, AclEntryPermission.READ_NAMED_ATTRS); |
| put(WinNT.READ_CONTROL, AclEntryPermission.READ_ACL); |
| put(WinNT.FILE_WRITE_DATA, AclEntryPermission.WRITE_DATA); |
| put(WinNT.FILE_APPEND_DATA, AclEntryPermission.APPEND_DATA); |
| put(WinNT.FILE_WRITE_ATTRIBUTES, |
| AclEntryPermission.WRITE_ATTRIBUTES); |
| put(WinNT.FILE_WRITE_EA, AclEntryPermission.WRITE_NAMED_ATTRS); |
| put(WinNT.WRITE_DAC, AclEntryPermission.WRITE_ACL); |
| put(WinNT.WRITE_OWNER, AclEntryPermission.WRITE_OWNER); |
| put(WinNT.DELETE, AclEntryPermission.DELETE); |
| put(WinNT.FILE_DELETE_CHILD, AclEntryPermission.DELETE_CHILD); |
| put(WinNT.SYNCHRONIZE, AclEntryPermission.SYNCHRONIZE); |
| put(WinNT.FILE_EXECUTE, AclEntryPermission.EXECUTE); |
| } |
| }); |
| |
| /** The map of Acl entry flags from NT to AclEntryFlag. */ |
| private static final Map<Byte, AclEntryFlag> ACL_FLAGS_MAP = |
| Collections.unmodifiableMap(new HashMap<Byte, AclEntryFlag>() { |
| { |
| put(WinNT.OBJECT_INHERIT_ACE, AclEntryFlag.FILE_INHERIT); |
| put(WinNT.CONTAINER_INHERIT_ACE, AclEntryFlag.DIRECTORY_INHERIT); |
| put(WinNT.INHERIT_ONLY_ACE, AclEntryFlag.INHERIT_ONLY); |
| put(WinNT.NO_PROPAGATE_INHERIT_ACE, |
| AclEntryFlag.NO_PROPAGATE_INHERIT); |
| } |
| }); |
| |
| /** The map of Acl entry type from NT to AclEntryType. */ |
| private static final Map<Byte, AclEntryType> ACL_TYPE_MAP = |
| Collections.unmodifiableMap(new HashMap<Byte, AclEntryType>() { |
| { |
| put(WinNT.ACCESS_ALLOWED_ACE_TYPE, AclEntryType.ALLOW); |
| put(WinNT.ACCESS_DENIED_ACE_TYPE, AclEntryType.DENY); |
| } |
| }); |
| |
| private final Advapi32 advapi32; |
| private final Kernel32 kernel32; |
| private final Mpr mpr; |
| private final Netapi32Ex netapi32; |
| private final Shlwapi shlwapi; |
| |
| /** Constructor used for production. */ |
| public WindowsAclFileAttributeViews() { |
| this(Advapi32.INSTANCE, Kernel32.INSTANCE, Mpr.INSTANCE, |
| Netapi32Ex.INSTANCE,Shlwapi.INSTANCE); |
| } |
| |
| /** Constructor used by the tests. */ |
| @VisibleForTesting |
| WindowsAclFileAttributeViews(Advapi32 advapi32, Kernel32 kernel32, |
| Mpr mpr, Netapi32Ex netapi32, Shlwapi shlwapi) { |
| this.advapi32 = advapi32; |
| this.kernel32 = kernel32; |
| this.mpr = mpr; |
| this.netapi32 = netapi32; |
| this.shlwapi = shlwapi; |
| } |
| |
| /** |
| * Returns a container for the direct and inherited ACLs for |
| * the supplied file. |
| * |
| * @param path The file/folder to get the {@link AclFileAttributeViews} for |
| * @return AclFileAttributeViews of direct and inherited ACL entries |
| */ |
| public AclFileAttributeViews getAclViews(Path path) throws IOException { |
| String pathname = path.toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); |
| WinNT.ACCESS_ACEStructure[] aces = getFileSecurity(pathname, |
| WinNT.DACL_SECURITY_INFORMATION | |
| WinNT.PROTECTED_DACL_SECURITY_INFORMATION | |
| WinNT.UNPROTECTED_DACL_SECURITY_INFORMATION); |
| ImmutableList.Builder<AclEntry> inherited = ImmutableList.builder(); |
| ImmutableList.Builder<AclEntry> direct = ImmutableList.builder(); |
| |
| for (WinNT.ACCESS_ACEStructure ace : aces) { |
| AclEntry aclEntry = newAclEntry(ace); |
| if (aclEntry != null) { |
| if ((ace.AceFlags & WinNT.INHERITED_ACE) == WinNT.INHERITED_ACE) { |
| inherited.add(aclEntry); |
| } else { |
| direct.add(aclEntry); |
| } |
| } |
| } |
| |
| List<AclEntry> inheritedAcl = inherited.build(); |
| log.log(Level.FINEST, "Inherited ACL for {0}: {1}", |
| new Object[] { pathname, inheritedAcl }); |
| |
| List<AclEntry> directAcl = direct.build(); |
| log.log(Level.FINEST, "Direct ACL for {0}: {1}", |
| new Object[] { pathname, directAcl }); |
| |
| return new AclFileAttributeViews( |
| new SimpleAclFileAttributeView(directAcl), |
| new SimpleAclFileAttributeView(inheritedAcl)); |
| } |
| |
| /** |
| * Returns the access control list for the file share which contains |
| * the supplied file. |
| * |
| * @param path The file/folder to get the {@link AclFileAttributeView} for |
| * @return AclFileAttributeView of ACL entries imposed by the share |
| */ |
| public AclFileAttributeView getShareAclView(Path path) |
| throws IOException, UnsupportedOperationException { |
| if (shlwapi.PathIsUNC(path.toString())) { |
| log.log(Level.FINEST, "Using a UNC path."); |
| return getUncShareAclView(path.toString()); |
| } else if (shlwapi.PathIsNetworkPath(path.toString())) { |
| log.log(Level.FINEST, "Using a mapped drive."); |
| // Call WNetGetUniversalNameW with the size needed for |
| // UNIVERSAL_NAME_INFO. If WNetGetUniversalNameW returns ERROR_MORE_DATA |
| // that indicates that a larger buffer is needed. If this happens, make |
| // a second call to WNetGetUniversalNameW with a buffer big enough. |
| Memory buf = new Memory(1024); |
| IntByReference bufSize = new IntByReference((int) buf.size()); |
| int result = mpr.WNetGetUniversalNameW(path.getRoot().toString(), |
| Mpr.UNIVERSAL_NAME_INFO_LEVEL, buf, bufSize); |
| if (result == WinNT.ERROR_MORE_DATA) { |
| buf = new Memory(bufSize.getValue()); |
| result = mpr.WNetGetUniversalNameW(path.getRoot().toString(), |
| Mpr.UNIVERSAL_NAME_INFO_LEVEL, buf, bufSize); |
| } |
| if (result != WinNT.NO_ERROR) { |
| throw new IOException("Unable to get UNC path for the mapped path " + |
| path + ". Result: " + result); |
| } |
| |
| Mpr.UNIVERSAL_NAME_INFO info = new Mpr.UNIVERSAL_NAME_INFO(buf); |
| return getUncShareAclView(info.lpUniversalName); |
| } else { |
| log.log(Level.FINEST, "Using a local drive."); |
| return new SimpleAclFileAttributeView(Collections.<AclEntry>emptyList()); |
| } |
| // TODO(mifern): For a local drive, mapped and UNC the share Acl must also |
| // include the Acls from the config point to the root. |
| } |
| |
| private AclFileAttributeView getUncShareAclView(String uncPath) |
| throws IOException { |
| Matcher match = UNC_PATTERN.matcher(uncPath); |
| if (!match.find()) { |
| throw new IOException("The UNC path " + uncPath + " is not valid. " |
| + "A UNC path of the form \\\\<host>\\<share> is required."); |
| } |
| String host = match.group(1); |
| String share = match.group(2); |
| log.log(Level.FINEST, "UNC: host: {0}, share: {1}.", |
| new Object[] { host, share }); |
| return getShareAclView(host, share); |
| } |
| |
| private AclFileAttributeView getShareAclView(String host, String share) |
| throws IOException { |
| PointerByReference buf = new PointerByReference(); |
| |
| // Call NetShareGetInfo with a 502 to get the security descriptor of the |
| // share. The security descriptor contains the Acl details for the share |
| // that the adaptor needs. |
| int result = netapi32.NetShareGetInfo(host, share, 502, buf); |
| if (result != WinError.ERROR_SUCCESS) { |
| if (result == WinError.ERROR_ACCESS_DENIED) { |
| throw new IOException( |
| "The user does not have access to the share Acl information."); |
| } else if (result == WinError.ERROR_INVALID_LEVEL) { |
| throw new IOException( |
| "The value specified for the level parameter is not valid."); |
| } else if (result == WinError.ERROR_INVALID_PARAMETER) { |
| throw new IOException("A specified parameter is not valid."); |
| } else if (result == WinError.ERROR_NOT_ENOUGH_MEMORY) { |
| throw new IOException("Insufficient memory is available."); |
| } else if (result == LMErr.NERR_NetNameNotFound) { |
| throw new IOException("The share name does not exist."); |
| } else { |
| throw new IOException("Unable to the read share Acl. Error: " + |
| result); |
| } |
| } |
| |
| Netapi32Ex.SHARE_INFO_502 info = |
| new Netapi32Ex.SHARE_INFO_502(buf.getValue()); |
| netapi32.NetApiBufferFree(buf.getValue()); |
| |
| WinNT.SECURITY_DESCRIPTOR_RELATIVE sdr = |
| new WinNT.SECURITY_DESCRIPTOR_RELATIVE(info.shi502_security_descriptor); |
| WinNT.ACL dacl = sdr.getDiscretionaryACL(); |
| |
| ImmutableList.Builder<AclEntry> builder = ImmutableList.builder(); |
| for (WinNT.ACCESS_ACEStructure ace : dacl.getACEStructures()) { |
| AclEntry entry = newAclEntry(ace); |
| if (entry != null) { |
| builder.add(entry); |
| } |
| } |
| |
| List<AclEntry> acl = builder.build(); |
| if (log.isLoggable(Level.FINEST)) { |
| log.log(Level.FINEST, "Share ACL for \\\\{0}\\{1}: {2}", |
| new Object[] { host, share, acl.toString() }); |
| } |
| return new SimpleAclFileAttributeView(acl); |
| } |
| |
| /** |
| * Creates an {@link AclEntry} from a {@code WinNT.ACCESS_ACEStructure}. |
| * |
| * @param ace Windows ACE returned by JNA |
| * @return AclEntry representing the ace, or {@code null} if a valid |
| * AclEntry could not be created from the ace. |
| */ |
| public AclEntry newAclEntry(WinNT.ACCESS_ACEStructure ace) { |
| // Map the type. |
| AclEntryType aclType = ACL_TYPE_MAP.get(ace.AceType); |
| if (aclType == null) { |
| log.log(Level.FINEST, "Skipping ACE with unsupported access type: {0}.", |
| ace.AceType); |
| return null; |
| } |
| |
| // Map the user. |
| Account account; |
| try { |
| account = getAccountBySid(ace.getSID()); |
| } catch (Win32Exception e) { |
| // Only the least significant 16-bits signifies the HR code. |
| if ((e.getHR().intValue() & 0xFFFF) == WinError.ERROR_NONE_MAPPED) { |
| log.log(Level.WARNING, "Skipping ACE with unresolvable SID: {0}.", |
| ace.getSidString()); |
| return null; |
| } else { |
| throw e; |
| } |
| } |
| String accountName = (account.domain == null ? |
| account.name : account.domain + "\\" + account.name); |
| UserPrincipal aclPrincipal; |
| String accountType = getSidTypeString(account.accountType); |
| if (USER_SID_TYPES.contains(account.accountType)) { |
| aclPrincipal = new User(accountName, accountType); |
| } else if (GROUP_SID_TYPES.contains(account.accountType)) { |
| aclPrincipal = new Group(accountName, accountType); |
| } else { |
| log.log(Level.FINEST, |
| "Skipping ACE with unsupported account type {0} ({1}).", |
| new Object[] { accountName, accountType }); |
| return null; |
| } |
| |
| // Expand NT GENERIC_* permissions to their FILE_GENERIC_* equivalents. |
| int aceMask = ace.Mask; |
| for (Map.Entry<Integer, Integer> e : GENERIC_PERMS_MAP.entrySet()) { |
| if ((ace.Mask & e.getKey()) == e.getKey()) { |
| aceMask |= e.getValue(); |
| } |
| } |
| |
| // Map the permissions. |
| Set<AclEntryPermission> aclPerms = EnumSet.noneOf(AclEntryPermission.class); |
| for (Map.Entry<Integer, AclEntryPermission> e : ACL_PERMS_MAP.entrySet()) { |
| if ((aceMask & e.getKey()) == e.getKey()) { |
| aclPerms.add(e.getValue()); |
| } |
| } |
| |
| // Map the flags. |
| Set<AclEntryFlag> aclFlags = EnumSet.noneOf(AclEntryFlag.class); |
| for (Map.Entry<Byte, AclEntryFlag> e : ACL_FLAGS_MAP.entrySet()) { |
| if ((ace.AceFlags & e.getKey()) == e.getKey()) { |
| aclFlags.add(e.getValue()); |
| } |
| } |
| |
| return AclEntry.newBuilder() |
| .setType(aclType) |
| .setPrincipal(aclPrincipal) |
| .setPermissions(aclPerms) |
| .setFlags(aclFlags) |
| .build(); |
| } |
| |
| @VisibleForTesting |
| Account getAccountBySid(WinNT.PSID sid) throws Win32Exception { |
| return Advapi32Util.getAccountBySid(sid); |
| } |
| |
| // One-to-one corresponance to WinNT.SID_NAME_USE "enumeration". |
| private static final List<String> SID_TYPE_NAMES = ImmutableList.of( |
| "Unknown", "User", "Group", "Domain", "Alias", "Well-known Group", |
| "Deleted", "Invalid", "Computer"); |
| |
| private static String getSidTypeString(int sidType) { |
| if (sidType < 0 || sidType > SID_TYPE_NAMES.size()) { |
| return SID_TYPE_NAMES.get(0); |
| } else { |
| return SID_TYPE_NAMES.get(sidType); |
| } |
| } |
| |
| private static class User implements UserPrincipal { |
| private final String accountName; |
| private final String accountType; |
| |
| User(String accountName, String accountType) { |
| this.accountName = accountName; |
| this.accountType = accountType; |
| } |
| |
| @Override |
| public String getName() { |
| return accountName; |
| } |
| |
| @Override |
| public String toString() { |
| return accountName + " (" + accountType + ")"; |
| } |
| } |
| |
| private static class Group extends User implements GroupPrincipal { |
| Group(String accountName, String accountType) { |
| super(accountName, accountType); |
| } |
| } |
| |
| @VisibleForTesting |
| public static interface Mpr extends StdCallLibrary { |
| Mpr INSTANCE = (Mpr) Native.loadLibrary("Mpr", Mpr.class, |
| W32APIOptions.UNICODE_OPTIONS); |
| |
| public final int UNIVERSAL_NAME_INFO_LEVEL = 1; |
| |
| int WNetGetUniversalNameW(String lpLocalPath, int dwInfoLevel, |
| Pointer lpBuffer, IntByReference lpBufferSize); |
| |
| public static class UNIVERSAL_NAME_INFO extends Structure { |
| public String lpUniversalName; |
| |
| public UNIVERSAL_NAME_INFO() { |
| super(); |
| } |
| |
| public UNIVERSAL_NAME_INFO(Pointer memory) { |
| useMemory(memory); |
| read(); |
| } |
| |
| @Override |
| protected List<String> getFieldOrder() { |
| return Arrays.asList(new String[] { "lpUniversalName" }); |
| } |
| } |
| } |
| |
| /** Uses JNA to call native Windows {@code GetFileSecurity} function. */ |
| private WinNT.ACCESS_ACEStructure[] getFileSecurity(String pathname, |
| int daclType) throws IOException { |
| WString wpath = new WString(pathname); |
| IntByReference lengthNeeded = new IntByReference(); |
| |
| if (advapi32.GetFileSecurity(wpath, daclType, null, 0, lengthNeeded)) { |
| throw new RuntimeException("GetFileSecurity was expected to fail with " |
| + "ERROR_INSUFFICIENT_BUFFER"); |
| } |
| |
| int rc = kernel32.GetLastError(); |
| if (rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) { |
| throw new IOException("Failed GetFileSecurity", new Win32Exception(rc)); |
| } |
| |
| Memory memory = new Memory(lengthNeeded.getValue()); |
| if (!advapi32.GetFileSecurity(wpath, daclType, memory, (int) memory.size(), |
| lengthNeeded)) { |
| throw new IOException("Failed GetFileSecurity", |
| new Win32Exception(kernel32.GetLastError())); |
| } |
| |
| WinNT.SECURITY_DESCRIPTOR_RELATIVE securityDescriptor = |
| new WinNT.SECURITY_DESCRIPTOR_RELATIVE(memory); |
| return securityDescriptor.getDiscretionaryACL().getACEStructures(); |
| } |
| } |