blob: 199b568c7655a71ea5fb43e29ae7c8193c9b9d74 [file] [log] [blame]
// 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.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());
private static final Kernel32 KERNEL32 = Kernel32.INSTANCE;
private static final Advapi32 ADVAPI32 = Advapi32.INSTANCE;
/** 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);
}
});
/**
* 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.INSTANCE.PathIsUNC(path.toString())) {
log.log(Level.FINEST, "Using a UNC path.");
return getUncShareAclView(path.toString());
} else if (Shlwapi.INSTANCE.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.
Mpr mprlib = Mpr.INSTANCE;
Memory buf = new Memory(1024);
IntByReference bufSize = new IntByReference((int) buf.size());
int result = mprlib.WNetGetUniversalNameW(path.getRoot().toString(),
Mpr.UNIVERSAL_NAME_INFO_LEVEL, buf, bufSize);
if (result == WinNT.ERROR_MORE_DATA) {
buf = new Memory(bufSize.getValue());
result = Mpr.INSTANCE.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 {
Netapi32Ex netapi32 = Netapi32Ex.INSTANCE;
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 static 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 = Advapi32Util.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();
}
// 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);
}
}
private 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();
}
}