Skip to content

Commit

Permalink
Merge pull request #1 from AgNO3/GregBragg-Add_symlink_functionality
Browse files Browse the repository at this point in the history
Greg bragg add symlink functionality
  • Loading branch information
GregBragg authored Feb 1, 2023
2 parents 2a52d08 + fd5bd47 commit 602f81f
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 113 deletions.
13 changes: 13 additions & 0 deletions src/main/java/jcifs/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -787,4 +787,17 @@ public interface Configuration {
* @return whether to permit guest logins when user authentication is requested
*/
boolean isAllowGuestFallback ();


/**
* Property <tt>jcifs.smb.client.symlinkBehavior</tt>, defaults to THROW
*
* See {@link SymlinkBehavior}
*
* Currently, automatic symlink following is only supported when using
* SMB 3.1.1 and only for symlinks pointing to the same share.
*
* @return how to handle encountered symlinks
*/
SymlinkBehavior getSymlinkBehavior();
}
35 changes: 35 additions & 0 deletions src/main/java/jcifs/SymlinkBehavior.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* © 2023 Moritz Bechler
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcifs;

/**
* Symlink behavior
*
*/
public enum SymlinkBehavior {
/**
* Will throw a {@link jcifs.smb.SmbSymlinkException} containing the target information when
* encountering a symlink
*/
THROW,

/**
* Automatically resolve symlinks that are located on the same share
*/
RESOLVE_SAME_SHARE
}
14 changes: 7 additions & 7 deletions src/main/java/jcifs/config/BaseConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,10 @@
import java.util.StringTokenizer;
import java.util.TimeZone;

import jcifs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcifs.CIFSException;
import jcifs.Configuration;
import jcifs.DialectVersion;
import jcifs.ResolverType;
import jcifs.SmbConstants;


/**
* @author mbechler
Expand Down Expand Up @@ -140,7 +135,7 @@ public class BaseConfiguration implements Configuration {
protected String guestUsername = "GUEST";
protected String guestPassword = "";
protected boolean allowGuestFallback = false;

protected SymlinkBehavior symlinkBehavior = SymlinkBehavior.THROW;

/**
* @throws CIFSException
Expand Down Expand Up @@ -621,6 +616,11 @@ public boolean isAllowGuestFallback () {
}


@Override
public SymlinkBehavior getSymlinkBehavior() {
return this.symlinkBehavior;
}

@Override
public byte[] getMachineId () {
return this.machineId;
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/jcifs/config/DelegatingConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import jcifs.Configuration;
import jcifs.DialectVersion;
import jcifs.ResolverType;
import jcifs.SymlinkBehavior;


/**
Expand Down Expand Up @@ -914,4 +915,9 @@ public String getGuestPassword () {
public boolean isAllowGuestFallback () {
return this.delegate.isAllowGuestFallback();
}

@Override
public SymlinkBehavior getSymlinkBehavior() {
return this.delegate.getSymlinkBehavior();
}
}
8 changes: 3 additions & 5 deletions src/main/java/jcifs/config/PropertyConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@
import java.net.InetAddress;
import java.util.Properties;

import jcifs.CIFSException;
import jcifs.Config;
import jcifs.Configuration;
import jcifs.DialectVersion;
import jcifs.SmbConstants;
import jcifs.*;


/**
Expand Down Expand Up @@ -145,6 +141,8 @@ public PropertyConfiguration ( Properties p ) throws CIFSException {
this.guestUsername = p.getProperty("jcifs.smb.client.guestUsername", "JCIFSGUEST");
this.guestPassword = p.getProperty("jcifs.smb.client.guestPassword", "");

this.symlinkBehavior = SymlinkBehavior.valueOf(p.getProperty("jcifs.smb.client.symlinkBehavior", "THROW"));

String minVer = p.getProperty("jcifs.smb.client.minVersion");
String maxVer = p.getProperty("jcifs.smb.client.maxVersion");

Expand Down
28 changes: 0 additions & 28 deletions src/main/java/jcifs/internal/smb2/Smb2ErrorContextResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,34 +64,6 @@ protected int readErrorContextResponse ( byte[] buffer ) {
return bufferIndex;
}

/**
* 2.2.2.2.1 Symbolic Link Error Response
*
* The Symbolic Link Error Response is used to indicate that a symbolic link was encountered on
* create; it describes the target path that the client MUST use if it requires to follow the
* symbolic link. This structure is contained in the ErrorData section of the SMB2 ERROR
* Response (section 2.2.2). This structure MUST NOT be returned in an SMB2 ERROR Response
* unless the Status code in the header of that response is set to STATUS_STOPPED_ON_SYMLINK.
*
* @param buffer
* @return The length, in bytes, of the response
* @throws Smb2ProtocolDecodingException
*/
public abstract int readSymLinkErrorResponse ( byte[] buffer ) throws SMBProtocolDecodingException;

/**
* 2.2.2.2.2 Share Redirect Error Context Response
*
* Servers which negotiate SMB 3.1.1 or higher can return this error context to a client in
* response to a tree connect request with the SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER bit set
* in the Flags field of the SMB2 TREE_CONNECT request. The corresponding Status code in the
* SMB2 header of the response MUST be set to STATUS_BAD_NETWORK_NAME. The error context data is
* formatted as follows.
*
* @param buffer
* @return The length, in bytes, of the response
* @throws SMBProtocolDecodingException
*/
public abstract int readShareRedirectErrorContextResponse ( byte[] buffer ) throws SMBProtocolDecodingException;

}
30 changes: 28 additions & 2 deletions src/main/java/jcifs/internal/smb2/Smb2ErrorDataFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,19 @@ public class Smb2ErrorDataFormat extends Smb2ErrorContextResponse {
private String substituteName;
private String printName;

@Override
/**
* 2.2.2.2.1 Symbolic Link Error Response
*
* The Symbolic Link Error Response is used to indicate that a symbolic link was encountered on
* create; it describes the target path that the client MUST use if it requires to follow the
* symbolic link. This structure is contained in the ErrorData section of the SMB2 ERROR
* Response (section 2.2.2). This structure MUST NOT be returned in an SMB2 ERROR Response
* unless the Status code in the header of that response is set to STATUS_STOPPED_ON_SYMLINK.
*
* @param buffer
* @return The length, in bytes, of the response
* @throws SMBProtocolDecodingException
*/
public int readSymLinkErrorResponse ( byte[] buffer ) throws SMBProtocolDecodingException {
int bufferIndex = super.readErrorContextResponse(buffer);
if ( this.errorId != 0 ) {
Expand Down Expand Up @@ -133,7 +145,21 @@ public String getPrintName () {
}


@Override


/**
* 2.2.2.2.2 Share Redirect Error Context Response
*
* Servers which negotiate SMB 3.1.1 or higher can return this error context to a client in
* response to a tree connect request with the SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER bit set
* in the Flags field of the SMB2 TREE_CONNECT request. The corresponding Status code in the
* SMB2 header of the response MUST be set to STATUS_BAD_NETWORK_NAME. The error context data is
* formatted as follows.
*
* @param buffer
* @return The length, in bytes, of the response
* @throws SMBProtocolDecodingException
*/
public int readShareRedirectErrorContextResponse ( byte[] buffer )
throws SMBProtocolDecodingException {
throw new UnsupportedOperationException("Not implemented");
Expand Down
55 changes: 48 additions & 7 deletions src/main/java/jcifs/internal/smb2/Smb2SymLinkResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
*/
package jcifs.internal.smb2;

import java.nio.charset.Charset;
import java.util.List;

import jcifs.SymlinkBehavior;
import jcifs.internal.SMBProtocolDecodingException;
import jcifs.internal.smb2.create.Smb2CreateResponse;
import jcifs.smb.SmbSymlinkException;
import jcifs.smb.SmbTreeHandleInternal;
import jcifs.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcifs.internal.SMBProtocolDecodingException;
import jcifs.util.Strings;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;

/**
* Defines methods to resolve a symlink target path.
Expand Down Expand Up @@ -80,13 +85,13 @@ private String resolveSymLinkTarget ( String originalFileName ) {


private String getSymLinkParsedPath ( String fileName, int unparsedPathLength ) {
byte[] fileNameBytes = fileName.getBytes(Charset.forName("UTF-16LE"));
byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_16LE);
return new String(fileNameBytes, 0, fileNameBytes.length - unparsedPathLength, Charset.forName("UTF-16LE"));
}


private String getSymLinkUnparsedPath ( String fileName, int unparsedPathLength ) {
byte[] fileNameBytes = fileName.getBytes(Charset.forName("UTF-16LE"));
byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_16LE);
return new String(fileNameBytes, fileNameBytes.length - unparsedPathLength, unparsedPathLength, Charset.forName("UTF-16LE"));
}

Expand All @@ -111,4 +116,40 @@ private String normalizePath ( String path ) {
return Strings.join(parts, '\\');
}

public String processSymlinkError(SmbTreeHandleInternal th, Smb2CreateResponse cr) throws SMBProtocolDecodingException, SmbSymlinkException {
String targetPath = this.parseSymLinkErrorData(cr.getFileName(), cr.getErrorData());

if (SymlinkBehavior.THROW == th.getConfig().getSymlinkBehavior()) {
throw new SmbSymlinkException(targetPath, th.getSession().getContext());
}

String lcaseTgt = targetPath.toLowerCase(Locale.ROOT);
if (lcaseTgt.startsWith("\\??\\unc\\")) {
// this symlink references a UNC path, this can only be (easily) handled
// if the target resides on the share and we simply can adjust the request
// path. For more extensive support, the tree connection needs to be
// switched.

// cut off \\??\\unc\\ prefix
targetPath = targetPath.substring(8);
String curShareUnc = th.getRemoteHostName().toLowerCase(Locale.ROOT) +
"\\" + th.getConnectedShare().toLowerCase(Locale.ROOT) + "\\";

if ( !targetPath.startsWith(curShareUnc)) {
// TODO: convert to URL
throw new SmbSymlinkException(targetPath, th.getSession().getContext());
}

// return path below share
return targetPath.substring(curShareUnc.length());
} else if (targetPath.startsWith("\\??\\")) {
// this is a link to local file, cut off prefix \??\
// handling is up to the user
// TODO: convert to file URL?
throw new SmbSymlinkException(targetPath.substring(4), th.getSession().getContext());
} else {
// this is a relative symlink
return targetPath;
}
}
}
38 changes: 11 additions & 27 deletions src/main/java/jcifs/smb/DirFileEntryEnumIterator2.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,16 @@
package jcifs.smb;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcifs.CIFSException;
import jcifs.Configuration;
import jcifs.DialectVersion;
import jcifs.ResourceNameFilter;
import jcifs.SmbConstants;
import jcifs.SmbResource;
import jcifs.internal.SMBProtocolDecodingException;
import jcifs.*;
import jcifs.internal.smb2.Smb2SymLinkResolver;
import jcifs.internal.smb2.create.Smb2CloseRequest;
import jcifs.internal.smb2.create.Smb2CreateRequest;
import jcifs.internal.smb2.create.Smb2CreateResponse;
import jcifs.internal.smb2.info.Smb2QueryDirectoryRequest;
import jcifs.internal.smb2.info.Smb2QueryDirectoryResponse;
import jcifs.internal.util.RecursionLimiter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
Expand Down Expand Up @@ -92,7 +85,7 @@ protected FileEntry open () throws CIFSException {

/**
*
* @param path
* @param uncPath
* @return
* @throws CIFSException
*/
Expand Down Expand Up @@ -125,24 +118,15 @@ private FileEntry open ( String uncPath ) throws CIFSException {

// We hit a symbolic link, parse the error data and resend for the 'real' directory or file path
if (cr != null && cr.isReceived() && cr.getStatus() == NtStatus.NT_STATUS_STOPPED_ON_SYMLINK) {

if (config.getMinimumVersion() != DialectVersion.SMB311) {
throw new SMBProtocolDecodingException(
"Configuration must be set to a minimum of version SMB 3.1.1 for property "
+ "'jcifs.smb.client.minVersion' to resolve symbolic link target path");
if (!th.getSession().unwrap(SmbSessionInternal.class).getTransport().unwrap(SmbTransportInternal.class).getSelectedDialect().atLeast(DialectVersion.SMB311)) {
// symlink error context information is only available since SMB3.1.1
// TODO: might be able to get the target information via some IOCTL for earlier versions?
throw e;
}

Smb2SymLinkResolver resolver = new Smb2SymLinkResolver();
try {
Smb2SymLinkResolver resolver = new Smb2SymLinkResolver();
String targetPath = resolver.parseSymLinkErrorData(cr.getFileName(), cr.getErrorData());

if (targetPath.startsWith("\\??\\")) {
throw new SMBProtocolDecodingException("SymLink target is a local path: " + targetPath);
} else {
return open(targetPath);
}
}
catch ( CIFSException | RuntimeException e3 ) {
return open(resolver.processSymlinkError(th, cr));
} catch ( CIFSException | RuntimeException e3 ) {
log.error("Exception thrown while processing symbolic link error data", e3);
e.addSuppressed(e3);
}
Expand Down
Loading

0 comments on commit 602f81f

Please sign in to comment.