smbj
smbj copied to clipboard
Null Pointer when opening DFS link
Hello @hierynomus,
We have observed NPE while opening DFS link.
Caused by: java.lang.NullPointerException at com.hierynomus.smbj.share.DiskShare.getDiskEntry(DiskShare.java:133) at com.hierynomus.smbj.share.DiskShare.open(DiskShare.java:66) at com.hierynomus.smbj.share.DiskShare.openDirectory(DiskShare.java:151) at com.hierynomus.smbj.share.DiskShare.list(DiskShare.java:258) at com.hierynomus.smbj.share.DiskShare.list(DiskShare.java:231)
We have observed this behavior for some of the DFS paths only. I tried to debug the issue and below are some of the observations.
-
Getting NPE for DFS path //somehost/dfsshare/dfslink but working fine for //SOMEHOST/dfsshare/dfslink (only difference is the case). dfslink point to //server1/folder
-
When the library tries to resolve the share //somehost/dfsshare/dfslink (getting NPE for this path), the actual path and the target path are the same hence it does not reroute and nested session has not been created. The final path remains //somehost/dfsshare/dfslink after the resolution for which the "fileAttributes" are null, hence getting NPE.
-
When the library tries to resolve the share //SOMEHOST/dfsshare/dfslink (working fine), the actual path and the target path are different because of the case only so it reroutes and create a nested session. When a nested session is created a new dfs referral request has been sent as referral cache is null for the nested session. This time it resolves the path properly and after the resolution the final path is //server1/folder and for that "fileattributes" are not null and request is successful.
private SMB2CreateResponseContext resolveAndCreateFile(final SmbPath path,
final SMB2ImpersonationLevel impersonationLevel, final Set<AccessMask> accessMask,
final Set<FileAttributes> fileAttributes, final Set<SMB2ShareAccess> shareAccess,
final SMB2CreateDisposition createDisposition, final Set<SMB2CreateOptions> createOptions) {
try {
SMB2CreateResponseContext target = resolver.resolve(session, path, new PathResolver.ResolveAction<SMB2CreateResponseContext>(){
@Override
public SMB2CreateResponseContext apply(SmbPath target) {
DiskShare resolvedShare = rerouteIfNeeded(path, target);
return resolvedShare.createFileAndResolve(target, impersonationLevel, accessMask, fileAttributes,
shareAccess, createDisposition, createOptions); }
});
return target;
} catch (PathResolveException pre) {
throw new SMBApiException(pre.getStatus().getValue(), SMB2MessageCommandCode.SMB2_CREATE,
"Cannot resolve path " + path, pre);
}
}
We are not sure of this behavior. Any idea about it?
Is this similar as issue #697 ?
Note for me the issue described #697 is not fixed with latest 0.11.5 version.
Yes, it seems similar to #697. Can you please run by changing the case of the hostname. If you are getting the issue with hostname in lowercase then try to run it with hostname in upper case or vice versa. For us that's the reason and this is why we are not getting this error for every DFS share.
Hi @hierynomus,
We have debug it further and below is our analysis.
When the library tries to resolve the dfs link path e.g. //somehost/dfsshare/dfslink it makes an IO request which fails with STATUS_PATH_NOT_COVERED. As per the documentation the client MUST look up the path used for the I/O operation i.e //somehost/dfsshare/dfslink in ReferralCache but instead it is looking for dfs path prefix i.e. //somehost/dfsshare in the referral cache. Due to cache hit library does not issue a new DFS link referral request and the final path remains //host/dfs/link which should not be the case ideally.
Ideally it should look up the path used for the I/O operation i.e //somehost/dfsshare/dfslink in ReferralCache which will be a cache miss and in that case library should issue a dfs link referral request, process the response and update the referral cache with the correct entry //somehost/dfsshare/dfslink -> //server1/folder
public <T> T resolve(Session session, SMB2Packet responsePacket, final SmbPath smbPath,
final ResolveAction<T> action) throws PathResolveException {
// If the server does not support DFS, short circuit this path resolution.
if (!session.getConnection().getConnectionContext().supportsDFS()) {
return wrapped.resolve(session, responsePacket, smbPath, action);
}
if (smbPath.getPath() != null && responsePacket.getHeader().getStatusCode() == NtStatus.STATUS_PATH_NOT_COVERED.getValue()) {
logger.info("DFS Share {} does not cover {}, resolve through DFS", smbPath.getShareName(), smbPath);
return start(session, smbPath, new ResolveAction<T>() {
@Override
public T apply(SmbPath target) {
logger.info("DFS resolved {} -> {}", smbPath, target);
return action.apply(target);
}
});
} else if (smbPath.getPath() == null && NtStatus.isError(responsePacket.getHeader().getStatusCode())) {
logger.info("Attempting to resolve {} through DFS", smbPath);
return start(session, smbPath, action);
}
return wrapped.resolve(session, responsePacket, smbPath, action);
}
[MS-DFSC] section 3.1.5.1
3.1.5.1 I/O Operation to Target Fails with STATUS_PATH_NOT_COVERED
When an I/O operation that is issued to a link target fails with STATUS_PATH_NOT_COVERED (0xC0000257), the client MUST fail the original I/O request.
When an I/O operation issued to a DFS root target server in step 8 of section 3.1.4.1 fails with STATUS_PATH_NOT_COVERED (0xC0000257), it indicates that the portion of the DFS namespace accessed by the client is not contained in the DFS root target server.
To identify the DFS link targets that contain the required portion of the DFS namespace, the client MUST look up the path used for the I/O operation in ReferralCache. On a cache hit, the resulting ReferralCache entry MUST be used in further processing.
Otherwise, the client MUST obtain the file attributes of the DFS link as specified in [MS-CIFS] section 3.2.4.12 or [MS-SMB2] section 3.2.4.8 based on the protocol transport.
If the file attributes include FILE_ATTRIBUTE_REPARSE_POINT, the client MUST issue a DFS link referral request, as specified in section 3.1.4.2, providing as parameters "LINK", the DFS root target server specified by the TargetHint of the ReferralCache entry corresponding to the DFS namespace, UserCredentials, MaxOutputSize, and Path. The Path parameter MUST be set to the path in the I/O operation issued to the DFS root target in step 8 of section 3.1.4.1. The client MUST process the DFS referral response as specified in section 3.1.5.4.3, which will update the ReferralCache.<9> The resulting ReferralCache entry, if any, MUST be used in further processing.
Any inputs will be really helpful.
When referring to the [MS-DFSC], one thing that is not clear is when do we resolve the link if the referral cache has an entry for DFS_ROOT.
For example, in this case where the path is //host/dfs/link, and referral cache already has an entry for DFS_ROOT ie //host/dfs, then in step 8, the I/O operation should fail with STATUS_PATH_NOT_COVERED. Now since ReferralCache entry indicates root targets, we need to go to 3.5.1.1
As per documentation of 3.1.5.1
---
Any updates on this issue?
I ran into the same issue and looked into it. I can confirm @andershermansen's analysis.
So the problem is that if you open the root of the DFS share first, it will be cached. You do it for listing its content. Then you take a listed item (which is a DFS link) and try to open that path. In this case the root is returned from the cache instead of requesting the DFS link resolution from the server.
I believe the root cause is this:
https://github.com/hierynomus/smbj/blob/5a7766a5a1bdfc71c7d644081c7be83a1a5ca955/src/main/java/com/hierynomus/msdfsc/ReferralCache.java#L223-L232
I think the method should return null
when there is no cache hit instead of ENTRY_UPDATER.get(this)
(which is the last found node, the root node in our case). Returning null
works properly for me in my test env.
@hierynomus What is the idea behind returning the last known node when the requested item is not found among its children? In this case the algorithm should ask for the missing info from the server and build the referral tree further. Could it be fixed?
The mentioned workarounds (using upper/lower case hostname or share name) may only work because the algorithm thinks that it is a different host/share (case sensitive comparision) and requests for the DFS link info from the server. As far as I see, it happens in a Windows Domain (which sends upper/lower case hostnames mixed) but it does not seem really robust. The issue can simply be replicated with standalone Windows DFS share or Samba.
@hierynomus Any chance to fix this?