diff --git a/README.md b/README.md index 4da2c0a..e29ab65 100644 --- a/README.md +++ b/README.md @@ -245,3 +245,109 @@ func main() { } } ``` + + +### Readdir example via DFS ### + +```go +package main + +import ( + "fmt" + "net" + iofs "io/fs" + + "github.com/hirochachacha/go-smb2" +) + +func main() { + conn, err := net.Dial("tcp", "SERVERNAME:445") + if err != nil { + panic(err) + } + defer conn.Close() + + d := &smb2.Dialer{ + Initiator: &smb2.NTLMInitiator{ + User: "USERNAME", + Password: "PASSWORD", + }, + } + + s, err := d.Dial(conn) + if err != nil { + panic(err) + } + defer s.Logoff() + + fs, err := s.Mount("SHARENAME") + if err != nil { + panic(err) + } + defer fs.Umount() + + ipc, err := s.Mount("$IPC") + if err != nil { + panic(err) + } + defer ipc.Umount() + + + // Fetch the DFS list for the directory, and specify if its DFS link + targetList, err := ipc.GetDFSTargetList(s, "SHARENAME", "DFSDIR", false) + if err != nil { + panic(err) + } + + isLink := false + + targetList, err := ipc.GetDFSTargetList(session, sharename, dfsdir, false) + if err != nil { + t.Error("unexpected error: ", err) + } + + for _, target := range targetList { + + address := target.TargetAddress + actualTargetFolder := target.TargetFolder + + //In case of non dfs links, what we get in Target folder is the base address of dfs folder. We need to append + //the directory name to reach the actual target + if len(target.TargetFolder) > 0 && !isLink { + actualTargetFolder = fmt.Sprintf("%s//%s", target.TargetFolder, dirname) + } else if !isLink { + actualTargetFolder = dirname + } + CONNECT := fmt.Sprintf("%s:%d", address, 445) + conn, err := net.Dial("tcp", CONNECT) + if err != nil { + panic(err) + } + defer conn.Close() + + d := &smb2.Dialer{ + Initiator: &smb2.NTLMInitiator{ + User: "USERNAME", + Password: "PASSWORD", + }, + } + + s, err := d.Dial(conn) + if err != nil { + panic(err) + } + defer s.Logoff() + + sh, err := s.Mount(target.TargetShare) + if err != nil { + panic(err) + } + + _, err = sh.ReadDir(actualTargetFolder) + if err == nil { + break + } + } +} + +``` diff --git a/client.go b/client.go index e0f7b36..325c781 100644 --- a/client.go +++ b/client.go @@ -2,6 +2,7 @@ package smb2 import ( "context" + "errors" "fmt" "io" "math/rand" @@ -224,7 +225,9 @@ func (c *Session) ListSharenames() ([]string, error) { // Share represents a SMB tree connection with VFS interface. type Share struct { *treeConn - ctx context.Context + ctx context.Context + dfsTargetList map[string][]*DFSTarget //For caching the DFS targets for a path + mapWriterLock *sync.RWMutex } func (fs *Share) WithContext(ctx context.Context) *Share { @@ -1022,6 +1025,181 @@ func (fs *Share) loanCredit(payloadSize int) (creditCharge uint16, grantedPayloa return fs.session.conn.loanCredit(payloadSize, fs.ctx) } +// DFSTarget response struct for a DFS request +type DFSTarget struct { + TargetAddress string + TargetShare string + TargetFolder string + + //These are required for preparing the target folder addresses + dfsPath string + networkAddressPath string +} + +func parseFQDNFromConnection(networkAddress string) string { + + strSlices := strings.Split(networkAddress, ":") + // fqdn should be always present + if len(strSlices[0]) == 0 { + panic(1) + } + fqdn := strSlices[0] + return fqdn +} + +// parseNetworkAddress returns the targetAddress, targetShare, targetFolder +func parseNetworkAddress(reqFileName, dfspath, networkAddress string) *DFSTarget { + networkPath := strings.Split(networkAddress, "\\") + + // 0 1 2 3 + //networkPath format: \WIN-M9SV8N08256\SmallDataSet\folderone + if len(networkPath) < 3 { + panic(-1) + } + + dfsTarget := &DFSTarget{ + dfsPath: dfspath, + networkAddressPath: networkAddress, + } + + // networkAddress is of the following format //ServerName//Share// + dfsTarget.TargetAddress = networkPath[1] + dfsTarget.TargetShare = networkPath[2] + + //actual dirname is formed from the two components. + //First part is from the network address, it has the base dfs link path + //Second part if from the requested file name. For that we are preparing it + //from the combination of secondPath = reqFileName-dfsPath + //dirname = optional foldername + secondPath + i := 3 + dirname := "" + //create the initial directory name from network address path + for i < len(networkPath) { + if len(dirname) > 0 { + dirname = fmt.Sprintf("%s\\%s", dirname, networkPath[i]) + } else { + dirname = networkPath[i] + } + i++ + } + + //create the target folder based on the requestFileName-dfsPath + dfsPathList := strings.Split(dfspath, "\\") + reqFileNameList := strings.Split(reqFileName, "\\") + + if len(dfsPathList) > len(reqFileNameList) { + fmt.Println("Incorrect assumption that dfsPath is not a subset of request file name") + panic(-1) + } + + targetIndex := len(dfsPathList) + + for targetIndex < len(reqFileNameList) { + if len(dirname) > 0 { + dirname = fmt.Sprintf("%s\\%s", dirname, reqFileNameList[targetIndex]) + } else { + dirname = reqFileNameList[targetIndex] + } + targetIndex++ + } + + dfsTarget.TargetFolder = dirname + return dfsTarget +} + +func getFirstChild(dfsname, dirname string, isLink bool) string { + if !isLink { + return dfsname + } + dirList := strings.Split(dirname, "\\") + if len(dirList) > 1 { + return dirList[0] + } + return dirname +} + +/* + GetDFSTargetList - This function fetches the DFS target for a directory. This is to invoked on $IPC share only. + +INPUT: + 1. *Session --> This is required to get the target server name. + 2. sharename --> As this function is to be exposed on $IPC share, this function requires the sharename on which the DFS exists. + 3. dirname --> directory name for which the DFS target is to be fetched + 4. isLink --> If the target directory is a DFS Link + +OUTPUT: + 1. []DFSTarget --> This is the list of folder targets(referrals) where this directory is present. + 2. error --> Error if we fetch DFS operation fails. +*/ +func (fs *Share) GetDFSTargetList(c *Session, sharename, dirname string, isLink bool) ([]*DFSTarget, error) { + + dfsname := fmt.Sprintf("\\%s\\%s", parseFQDNFromConnection(c.addr), sharename) + if isLink { + dfsname = fmt.Sprintf("\\%s\\%s\\%s", parseFQDNFromConnection(c.addr), sharename, dirname) + } + + // this is for handling multiple calls + fs.mapWriterLock.Lock() + defer fs.mapWriterLock.Unlock() + + //check if its present in the map + //TODO: Add the TTL for this target and check here. Otherwise invalidate this cache entry + cachedTargetList, ok := fs.dfsTargetList[getFirstChild(dfsname, dirname, isLink)] + if ok { + var actualTargetForEntry []*DFSTarget + for _, i := range cachedTargetList { + actualTargetForEntry = append(actualTargetForEntry, parseNetworkAddress(dfsname, i.dfsPath, i.networkAddressPath)) + } + return actualTargetForEntry, nil + } + + //Note: For Windows based DFS systems - For converting dirname as Request file name, we need to append one extra space. If not done, response + //is not formed properly. This is not requried for DFS based of Nutanix Machines + dfsRefReq := DFSReferralRequest{ + MaxReferralLevel: 4, + RequestFileName: fmt.Sprintf("%s ", dfsname), // Note this is the tweak for Windows DFS vs Nutanix DFS + //RequestFileName: fmt.Sprintf("%s", dfsname), // Note this will work with Non Windows DFS + } + + //prepare the IOCTL request body + ioctl := IoctlRequest{ + CtlCode: FSCTL_DFS_GET_REFERRALS, + Flags: SMB2_0_IOCTL_IS_FSCTL, + Input: &dfsRefReq, + FileId: &FileId{ + //In case of DFS call, FileID should be 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF + Persistent: DFSFileID, + Volatile: DFSFileID, + }, + MaxOutputResponse: 4096, + } + + //send the request + output, err := fs.sendRecv(SMB2_IOCTL, &ioctl) + if err != nil { + return nil, err + } + + r := IoctlResponseDecoder(output) + if r.IsInvalid() { + return nil, errors.New("failed to parse response") + } + + //parse the response and come up with dfs target list + //TODO: need to think abour the error scenario's + dfsRespList := r.RespDFSReferral(dfsname) + + var dfsTargetList []*DFSTarget + for _, dfsResp := range dfsRespList { + dfsTargetList = append(dfsTargetList, parseNetworkAddress(dfsname, dfsResp.DfsPath, dfsResp.NetworkAddress)) + } + + //update the dfsTargetList map, so that in future we won't make this request + fs.dfsTargetList[getFirstChild(dfsname, dirname, isLink)] = dfsTargetList + + return dfsTargetList, nil +} + type File struct { fs *Share fd *FileId diff --git a/internal/smb2/const.go b/internal/smb2/const.go index 146c533..0642d31 100644 --- a/internal/smb2/const.go +++ b/internal/smb2/const.go @@ -7,6 +7,12 @@ const ( MAGIC2 = "\xfdSMB" ) +var ( + // When the IOCTL code is FSCTL_DFS_GET_REFERRALS or FSCTL_DFS_GET_REFERRALS_EX + // client should send the FileID 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF + DFSFileID = [8]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} +) + // ---------------------------------------------------------------------------- // SMB2 Packet Header // diff --git a/internal/smb2/request.go b/internal/smb2/request.go index 470a22e..dd71e6e 100644 --- a/internal/smb2/request.go +++ b/internal/smb2/request.go @@ -1010,6 +1010,23 @@ func (r CancelRequestDecoder) StructureSize() uint16 { return le.Uint16(r[:2]) } +// -------------------------------------------------------------------------- +// REQ_GET_DFS_REFERRAL -- DFS Referral Request +// +type DFSReferralRequest struct { + MaxReferralLevel uint16 + RequestFileName string +} + +func (d *DFSReferralRequest) Size() int { + return int(uint16(2) + uint16(utf16le.EncodedStringLen(d.RequestFileName))) +} + +func (d *DFSReferralRequest) Encode(b []byte) { + le.PutUint16(b, d.MaxReferralLevel) + utf16le.EncodeString(b[2:], d.RequestFileName) +} + // ---------------------------------------------------------------------------- // SMB2 IOCTL Request Packet // @@ -1048,7 +1065,7 @@ func (c *IoctlRequest) Encode(pkt []byte) { le.PutUint32(req[4:8], c.CtlCode) c.FileId.Encode(req[8:24]) le.PutUint32(req[32:36], c.MaxInputResponse) - le.PutUint32(req[36:40], c.OutputOffset) + //le.PutUint32(req[36:40], c.OutputOffset) le.PutUint32(req[40:44], c.OutputCount) le.PutUint32(req[44:48], c.MaxOutputResponse) le.PutUint32(req[48:52], c.Flags) @@ -1057,6 +1074,8 @@ func (c *IoctlRequest) Encode(pkt []byte) { if c.Input != nil { le.PutUint32(req[24:28], uint32(off+64)) // InputOffset + //outputOffset + le.PutUint32(req[36:40], uint32(off+64)) c.Input.Encode(req[off:]) @@ -1064,6 +1083,243 @@ func (c *IoctlRequest) Encode(pkt []byte) { } } +// RespDFSReferral parse the DFS response which came as a IOCTL payload +func (r IoctlResponseDecoder) RespDFSReferral(reqFileName string) []*CommonDFSResp { + output := r.Output() + respDfsReferral := &RespGetDFSReferral{} + + offset := 0 + respDfsReferral.PathConsumed = le.Uint16(output[offset : offset+2]) + offset += 2 + respDfsReferral.NumberOfReferrals = le.Uint16(output[offset : offset+2]) + offset += 2 + respDfsReferral.ReferralHeaderFlags = le.Uint16(output[offset : offset+2]) + offset += 2 + respDfsReferral.Padding = le.Uint16(output[offset : offset+2]) + offset += 2 + + referralEntry := output[offset:] + + rIndex := 0 + entryOffset := 0 + var dfsRespList []*CommonDFSResp + var dfsPath string + + var preNetworkStartPrefix int + for rIndex < int(respDfsReferral.NumberOfReferrals) { + var curStartOfnetworkStartPrefix int + versionNumber := le.Uint16(referralEntry[entryOffset : entryOffset+2]) + if versionNumber == 1 { + var v1 *DFSReferralV1 + v1, entryOffset = parseDFSReferralV1(referralEntry, entryOffset) + dfsRespList = append(dfsRespList, &CommonDFSResp{DfsPath: v1.DfsPath, NetworkAddress: v1.DfsPath}) + curStartOfnetworkStartPrefix = entryOffset + } else if versionNumber == 2 { + var v2 *DFSReferralV2 + v2, curStartOfnetworkStartPrefix, entryOffset = parseDFSReferralV2(referralEntry, entryOffset) + dfsPath = v2.DfsPath + } else if versionNumber == 3 { + var v3 *DFSReferralV3 + v3, curStartOfnetworkStartPrefix, entryOffset = parseDFSReferralV3(referralEntry, entryOffset) + dfsPath = v3.DfsPath + } else if versionNumber == 4 { + var v4 *DFSReferralV4 + v4, curStartOfnetworkStartPrefix, entryOffset = parseDFSReferralV4(referralEntry, entryOffset) + dfsPath = v4.DfsPath + } + + if rIndex > 0 && versionNumber != 1 { + preNetworkAddressPrefix := utf16le.DecodeToString(referralEntry[preNetworkStartPrefix:curStartOfnetworkStartPrefix]) + dfsRespList = append(dfsRespList, &CommonDFSResp{DfsPath: dfsPath, NetworkAddress: preNetworkAddressPrefix}) + + } + preNetworkStartPrefix = curStartOfnetworkStartPrefix + rIndex++ + } + + //For the last entry + preNetworkAddressPrefix := utf16le.DecodeToString(referralEntry[preNetworkStartPrefix:]) + if len(preNetworkAddressPrefix) > 0 { + dfsRespList = append(dfsRespList, &CommonDFSResp{DfsPath: dfsPath, NetworkAddress: preNetworkAddressPrefix}) + } + + return dfsRespList +} + +func parseDFSReferralV1(referralEntry []byte, entryOffset int) (*DFSReferralV1, int) { + dfsReferralV1 := &DFSReferralV1{} + basereferralOffset := entryOffset + dfsReferralV1.VersionNumber = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV1.Size = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV1.ServerType = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV1.ReferralEntryFlags = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + + dfsReferralV1.DfsPath = utf16le.DecodeToString(referralEntry[entryOffset : uint16(basereferralOffset)+dfsReferralV1.Size]) + entryOffset = basereferralOffset + int(dfsReferralV1.Size) + return dfsReferralV1, entryOffset +} + +func parseDFSReferralV2(referralEntry []byte, entryOffset int) (*DFSReferralV2, int, int) { + dfsReferralV2 := &DFSReferralV2{} + basereferralOffset := entryOffset + dfsReferralV2.VersionNumber = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV2.Size = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV2.ServerType = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV2.ReferralEntryFlags = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV2.Proximity = le.Uint32(referralEntry[entryOffset : entryOffset+4]) + entryOffset += 4 + dfsReferralV2.TimeToLive = le.Uint32(referralEntry[entryOffset : entryOffset+4]) + entryOffset += 4 + dfsReferralV2.DFSPathOffset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + dfsReferralV2.DFSAlternatePathOFfset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + dfsReferralV2.NetworkAddressOffset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + + dfsReferralV2.DfsPath = utf16le.DecodeToString(referralEntry[dfsReferralV2.DFSPathOffset:dfsReferralV2.DFSAlternatePathOFfset]) + dfsReferralV2.DfsAlternathPath = utf16le.DecodeToString(referralEntry[dfsReferralV2.DFSAlternatePathOFfset:dfsReferralV2.NetworkAddressOffset]) + + return dfsReferralV2, int(dfsReferralV2.NetworkAddressOffset), entryOffset +} + +func parseDFSReferralV3(referralEntry []byte, entryOffset int) (*DFSReferralV3, int, int) { + dfsReferralV3 := &DFSReferralV3{} + basereferralOffset := entryOffset + dfsReferralV3.VersionNumber = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV3.Size = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV3.ServerType = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV3.ReferralEntryFlags = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV3.TimeToLive = le.Uint32(referralEntry[entryOffset : entryOffset+4]) + entryOffset += 4 + dfsReferralV3.DFSPathOffset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + dfsReferralV3.DFSAlternatePathOFfset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + dfsReferralV3.NetworkAddressOffset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + dfsReferralV3.ServiceSiteGuid = utf16le.DecodeToString(referralEntry[entryOffset : entryOffset+16]) + entryOffset += 16 + + dfsReferralV3.DfsPath = utf16le.DecodeToString(referralEntry[dfsReferralV3.DFSPathOffset:dfsReferralV3.DFSAlternatePathOFfset]) + dfsReferralV3.DfsAlternathPath = utf16le.DecodeToString(referralEntry[dfsReferralV3.DFSAlternatePathOFfset:dfsReferralV3.NetworkAddressOffset]) + + return dfsReferralV3, int(dfsReferralV3.NetworkAddressOffset), entryOffset +} + +func parseDFSReferralV4(referralEntry []byte, entryOffset int) (*DFSReferralV4, int, int) { + dfsReferralV4 := &DFSReferralV4{} + + basereferralOffset := entryOffset + dfsReferralV4.VersionNumber = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV4.Size = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV4.ServerType = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV4.ReferralEntryFlags = le.Uint16(referralEntry[entryOffset : entryOffset+2]) + entryOffset += 2 + dfsReferralV4.TimeToLive = le.Uint32(referralEntry[entryOffset : entryOffset+4]) + entryOffset += 4 + dfsReferralV4.DFSPathOffset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + dfsReferralV4.DFSAlternatePathOFfset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + dfsReferralV4.NetworkAddressOffset = le.Uint16(referralEntry[entryOffset:entryOffset+2]) + uint16(basereferralOffset) + entryOffset += 2 + dfsReferralV4.ServiceSiteGuid = utf16le.DecodeToString(referralEntry[entryOffset : entryOffset+16]) + entryOffset += 16 + + dfsReferralV4.DfsPath = utf16le.DecodeToString(referralEntry[dfsReferralV4.DFSPathOffset:dfsReferralV4.DFSAlternatePathOFfset]) + dfsReferralV4.DfsAlternathPath = utf16le.DecodeToString(referralEntry[dfsReferralV4.DFSAlternatePathOFfset:dfsReferralV4.NetworkAddressOffset]) + + return dfsReferralV4, int(dfsReferralV4.NetworkAddressOffset), entryOffset +} + +// CommonDFSResp this is the common dfs response across different referral versions. Parsing of target +// address, shares is done on the layer above +type CommonDFSResp struct { + DfsPath string + NetworkAddress string +} + +//RespGetDFSReferral DFS Referral Response (Common part) +type RespGetDFSReferral struct { + PathConsumed uint16 + NumberOfReferrals uint16 + ReferralHeaderFlags uint16 + Padding uint16 +} + +// DFSReferralV1 DFS Referral version v1 response structure +type DFSReferralV1 struct { + VersionNumber uint16 + Size uint16 + ServerType uint16 + ReferralEntryFlags uint16 + DfsPath string +} + +// DFSReferralV2 DFS Referral version v2 response structure +type DFSReferralV2 struct { + VersionNumber uint16 + Size uint16 + ServerType uint16 + ReferralEntryFlags uint16 + Proximity uint32 + TimeToLive uint32 + DFSPathOffset uint16 + DFSAlternatePathOFfset uint16 + NetworkAddressOffset uint16 + DfsPath string + DfsAlternathPath string + DfsNetworkAddressPath string +} + +// DFSReferralV3 DFS Referral version v3 response structure +type DFSReferralV3 struct { + VersionNumber uint16 + Size uint16 + ServerType uint16 + ReferralEntryFlags uint16 + TimeToLive uint32 + DFSPathOffset uint16 + DFSAlternatePathOFfset uint16 + NetworkAddressOffset uint16 + ServiceSiteGuid string + DfsPath string + DfsAlternathPath string + DfsNetworkAddressPath string +} + +// DFSReferralV4 DFS Referral version v4 response structure +type DFSReferralV4 struct { + VersionNumber uint16 + Size uint16 + ServerType uint16 + ReferralEntryFlags uint16 + TimeToLive uint32 + DFSPathOffset uint16 + DFSAlternatePathOFfset uint16 + NetworkAddressOffset uint16 + ServiceSiteGuid string + DfsPath string + DfsAlternathPath string + DfsNetworkAddressPath string +} + type IoctlRequestDecoder []byte func (r IoctlRequestDecoder) IsInvalid() bool { diff --git a/smb2_test.go b/smb2_test.go index fbb37cb..fda9e30 100644 --- a/smb2_test.go +++ b/smb2_test.go @@ -57,13 +57,22 @@ type config struct { Conn connConfig `json:"conn,omitempty"` Session sessionConfig `json:"session,omitempty"` TreeConn treeConnConfig `json:"tree_conn"` + DFSDirectory string `json:"dfs_dir,omitempty"` } var cfg config var fs *smb2.Share var rfs *smb2.Share +var ipc *smb2.Share var session *smb2.Session var dialer *smb2.Dialer +var sharename string +var dfsdir string + +const ( + //TODO: Add comment + IPCShare = "$IPC" +) func connect(f func()) { { @@ -127,6 +136,14 @@ func connect(f func()) { } defer fs2.Umount() + ipc, err = c.Mount(IPCShare) + if err != nil { + panic(err) + } + defer ipc.Umount() + sharename = cfg.TreeConn.Share1 + dfsdir = cfg.DFSDirectory + fs = fs1 rfs = fs2 session = c @@ -880,3 +897,20 @@ func TestGlob(t *testing.T) { t.Errorf("unexpected matches: %v != %v", matches5, expected5) } } + +func TestGetDFSTarget(t *testing.T) { + if fs == nil || + ipc == nil || + len(dfsdir) == 0 { + t.Skip() + } + + dfsdir := "DIRNAME" + isLink := false + + _, err := ipc.GetDFSTargetList(session, sharename, dfsdir, isLink) + if err != nil { + t.Error("unexpected error: ", err) + } + +}