|
|
// Copyright (c) 1996-2002 Microsoft Corporation
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// File: sharenum.cxx
//
// Contents: The CShareEnumerator class implementation.
//
// Classes: CShareEnumerator
//
// History: 28-Jan-98 MikeHill Created
//
// Notes:
//
//--------------------------------------------------------------------------
#include "pch.cxx"
#pragma hdrstop
#include "trklib.hxx"
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::Initialize
//
// Synopsis: Initializes this enumeration by calling
// NetShareEnum.
//
// Arguments: [IDL_handle] (in)
// The RPC binding handle to the client on whom's
// behalf we're acting.
//
// [ptszMachineName] (in)
// The machine on which shares are to be enumerated.
// If this value is NULL, the local machine is assumed.
//
//
// Returns: None.
//
// Raises: On error.
//
//--------------------------------------------------------------------
VOID CShareEnumerator::Initialize( RPC_BINDING_HANDLE IDL_handle, const TCHAR *ptszMachineName ) { NET_API_STATUS netstatus; NETRESOURCE netresourceMachine; DWORD dwTotalEntries;
TrkAssert( !_fInitialized ); // Start with a clean state
_ClearCache(); _iCurrentEntry = static_cast<ULONG>(-1); _fInitialized = TRUE;
_IDL_handle = IDL_handle;
// Keep the machine name, retrieving it if necessary.
_tcscpy( _tszMachineName, TEXT("\\\\") );
if( NULL != ptszMachineName ) _tcscpy( &_tszMachineName[2], ptszMachineName ); else { DWORD cbMachineName = sizeof(_tszMachineName) - 2; if( !GetComputerName( &_tszMachineName[2], &cbMachineName )) TrkRaiseLastError(); }
// Start the enumeration. We'll simply get all the share information
// at once, instead of using a resume handle and making repeated RPC
// calls to the Server service.
netstatus = NetShareEnum( (TCHAR*) ptszMachineName, // Server (we must de-const it)
502, // Info level
(LPBYTE*) &_prgshare_info, // Return buffer
MAX_PREFERRED_LENGTH, // Get everything
&_cEntries, // Entries read
&dwTotalEntries, // Total entries avail
NULL ); // Resume handle
if( STATUS_SUCCESS != netstatus ) TrkRaiseWin32Error( HRESULT_FROM_WIN32(netstatus) );
TrkAssert( _cEntries == dwTotalEntries );
return;
}
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::UnInitialize
//
// Synopsis: Free any resources.
//
// Arguments: None.
//
// Returns: None.
//
// Raises: No
//
//--------------------------------------------------------------------
VOID CShareEnumerator::UnInitialize() { if( _fInitialized ) { if( NULL != _prgshare_info ) NetApiBufferFree( _prgshare_info );
InitLocals(); }
CTrkRpcConfig::UnInitialize(); }
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::_ClearCache
//
// Synopsis: Clear the data members of the CShareEnumerator
// which are cached information about the current share.
// (This is done in preparation to move on to the next
// share in the enumeration.)
//
// Arguments: None.
//
// Returns: None.
//
// Raises: No
//
//--------------------------------------------------------------------
VOID CShareEnumerator::_ClearCache() { // Clear the cached information about the
// current share.
_cchSharePath = (ULONG) -1; _ulMerit = 0; _enumHiddenState = HS_UNKNOWN;
}
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::Next
//
// Synopsis: Moves the enumerator on to the next share in the
// the enumeration.
//
// Arguments: None.
//
// Returns: TRUE if there was another element in the enumeration,
// FALSE if there are no more shares to enumerate.
//
// Raises: No.
//
//--------------------------------------------------------------------
BOOL CShareEnumerator::Next() { TrkAssert( _fInitialized );
if( _iCurrentEntry + 1 < _cEntries ) { _ClearCache(); _iCurrentEntry++; return( TRUE ); } else return( FALSE );
}
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::CoversDrivePath
//
// Synopsis: Determines if the current share in the enumeration
// "covers" a given drive path. E.g., a share to
// "c:\docs" covers the drive path "c:\docs\mydoc.doc".
//
// Arguments: [ptszDrivePath]
// The drive-based path to check for coverage.
//
// Returns: TRUE if the current share covers the given path,
// FALSE otherwise.
//
// Raises: No
//
//--------------------------------------------------------------------
BOOL CShareEnumerator::CoversDrivePath( const TCHAR *ptszDrivePath ) { TrkAssert( _fInitialized ); TrkAssert( TEXT(':') == ptszDrivePath[1] );
// Does the current share path cover the file? It does if it compares
// successfully with the local path, up to the entire length of the
// share path.
if( _IsValidShare() && QueryCCHSharePath() <= _tcslen(ptszDrivePath) && !_tcsnicmp( GetSharePath(), ptszDrivePath, QueryCCHSharePath() ) ) return TRUE; else return FALSE; }
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::_IsHiddenShare
//
// Synopsis: Determines if the current share is "hidden"
// (i.e., the share name ends in a '$').
//
// The first time this method is called, the share
// named is checked for hidden-ness. The result is
// cached for subsequent calls.
//
// Arguments: None.
//
// Returns: TRUE if the current share is hidden,
// FALSE if it is visible.
//
// Raises: On error.
//
//--------------------------------------------------------------------
BOOL CShareEnumerator::_IsHiddenShare() { TrkAssert( _fInitialized ); TrkAssert( _IsValidShare() ); TrkAssert( NULL != GetShareName() ); TrkAssert( TEXT('\0') != GetShareName()[0] );
if( _enumHiddenState == HS_UNKNOWN ) { _enumHiddenState = TEXT('$') == GetShareName()[ _tcslen(GetShareName()) - 1 ] ? HS_HIDDEN : HS_VISIBLE; }
return( HS_HIDDEN == _enumHiddenState ); }
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::_IsAdminShare
//
// Synopsis: Determines if the current share is Admin share.
// Admin shares are automatically created by the Server
// service during initialization. They're hard-coded
// to be A$, B$, C$, etc., for each of the drives,
// and ADMIN$ for the %windir% directory.
//
// Arguments: None.
//
// Returns: TRUE if the current share is an auto-generated admin share,
// FALSE otherwise.
//
//--------------------------------------------------------------------
BOOL CShareEnumerator::_IsAdminShare() { TCHAR tcDriveLetter; TrkAssert( _fInitialized ); TrkAssert( _IsValidShare() ); TrkAssert( NULL != GetShareName() ); TrkAssert( TEXT('\0') != GetShareName()[0] );
tcDriveLetter = TrkCharUpper( GetShareName()[0] );
// Check for the admin share characteristics.
if( 2 == _tcslen(GetShareName()) && TEXT('$') == GetShareName()[ 1 ] && TEXT('A') <= tcDriveLetter && TEXT('Z') >= tcDriveLetter ) { return( TRUE ); // It's a drive share
} else if( !_tcsicmp( TEXT("admin$"), GetShareName() )) return( TRUE ); // It's the %windir% share
else return( FALSE );
}
//+----------------------------------------------------------------------------
//
// Method: _IsValidShare
//
// Synopsis: Returns True if the current share is valid. A share
// valid it if it is in the form "<drive letter>:\\". E.g.
// "IPC$" isn't a valid share.
//
// Arguments: None
//
// Returns: None
//
//+----------------------------------------------------------------------------
inline BOOL CShareEnumerator::_IsValidShare() { TCHAR tcFirstChar;
// There should be a path, and it should be at least 3 characters
// (e.g. "D:\")
if( NULL == GetSharePath() || 3 > QueryCCHSharePath() ) return FALSE;
tcFirstChar = TrkCharUpper( GetSharePath()[0] );
// Make sure that the share path begins with "<Drive>:\\".
if( TEXT('A') <= tcFirstChar && tcFirstChar <= TEXT('Z') && TEXT(':') == GetSharePath()[1] && TEXT('\\') == GetSharePath()[2] ) { return( TRUE ); } else return( FALSE );
}
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::GetMerit
//
// Synopsis: Returns the linear (ULONG) merit of the current path.
// The greater this merit value, the more useful the share
// is. This is calculated on the first call to this method,
// and cached for use in subsequent calls.
//
// Arguments: None.
//
// Returns: None.
//
// Raises: On error.
//
//--------------------------------------------------------------------
ULONG CShareEnumerator::GetMerit() { TrkAssert( _fInitialized );
// The merit of a share is the bitwise-OR of the share's
// enumAccessLevel, its hidden-ness, and the length of
// the covered path.
//
// Shorter paths are more mertious than longer paths
// (because they cover more of the volume), so we subtract
// the path length from the max-value; thus giving shorter
// paths more weight.
if( 0 == _ulMerit && _IsValidShare() ) { _ulMerit = ( _GetAccessLevel() ) | ( SPC_MAX_COVERAGE - QueryCCHSharePath() ) | ( _IsHiddenShare() ? HS_HIDDEN : HS_VISIBLE ); }
TrkLog(( TRKDBG_MEND, TEXT("Score %d for %s (%s)"), _ulMerit, _prgshare_info[ _iCurrentEntry ].shi502_netname, _prgshare_info[ _iCurrentEntry ].shi502_path )); return( _ulMerit ); }
static void SDAllocHelper( BYTE **ppb, ULONG cbCurrent, ULONG cbRequired ) { if( cbRequired <= cbCurrent ) return;
*ppb = new BYTE[ cbRequired ]; if( NULL == *ppb ) { TrkLog((TRKDBG_ERROR, TEXT("Failed alloc in SDAllocHelper"))); TrkRaiseWin32Error( ERROR_NOT_ENOUGH_MEMORY ); }
return; }
void CShareEnumerator::_AbsoluteSDHelper( const PSECURITY_DESCRIPTOR pSDRelative, PSECURITY_DESCRIPTOR *ppSDAbs, ULONG *pcbSDAbs, PACL *ppDaclAbs, ULONG *pcbDaclAbs, PACL *ppSaclAbs, ULONG *pcbSaclAbs, PSID *ppSidOwnerAbs, ULONG *pcbSidOwnerAbs, PSID *ppSidGroupAbs, ULONG *pcbSidGroupAbs ) { ULONG cbSDAbs = *pcbSDAbs; ULONG cbDaclAbs = *pcbDaclAbs; ULONG cbSaclAbs = *pcbSaclAbs; ULONG cbSidOwnerAbs = *pcbSidOwnerAbs; ULONG cbSidGroupAbs = *pcbSidGroupAbs;
for( int i = 0; i < 2; i++ ) { if( !MakeAbsoluteSD( pSDRelative, *ppSDAbs, pcbSDAbs, *ppDaclAbs, pcbDaclAbs, *ppSaclAbs, pcbSaclAbs, *ppSidOwnerAbs, pcbSidOwnerAbs, *ppSidGroupAbs, pcbSidGroupAbs )) { if( i > 0 || ERROR_INSUFFICIENT_BUFFER != GetLastError() ) { TrkLog((TRKDBG_ERROR, TEXT("Couldn't make absolute SD"))); TrkRaiseLastError( ); }
TrkLog(( TRKDBG_MEND, TEXT("Realloc _AbsoluteSDHelper") ));
SDAllocHelper( (BYTE**) ppSDAbs, cbSDAbs, *pcbSDAbs ); SDAllocHelper( (BYTE**) ppDaclAbs, cbDaclAbs, *pcbDaclAbs ); SDAllocHelper( (BYTE**) ppSaclAbs, cbSaclAbs, *pcbSaclAbs ); SDAllocHelper( (BYTE**) ppSidOwnerAbs, cbSidOwnerAbs, *pcbSidOwnerAbs ); SDAllocHelper( (BYTE**) ppSidGroupAbs, cbSidGroupAbs, *pcbSidGroupAbs ); } }
}
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::_GetAccessLevel
//
// Synopsis: Determines the "access level" of the current share.
// The definition of an access level is provided by
// the enumAccessLevels enumeration in PShareMerit.
//
// Once calculated, this access level is not cached for
// subsequent calls, because this method is only called
// by GetMerit, which provides its own cacheing.
//
// *** Note: This is a temporary solution. This should
// be replaced with a solution that simply checks security
// on the share, without actually attempting to open the
// file.
//
// Arguments: None.
//
// Returns: An access level in the form of an enumAccessLevels.
//
// Raises: On error.
//
//--------------------------------------------------------------------
PShareMerit::enumAccessLevels CShareEnumerator::_GetAccessLevel() {
enumAccessLevels AccessLevel = AL_NO_ACCESS; static const int StackBufferSizes = 256;
TCHAR tszUNCPath[ MAX_PATH + 1 ]; HANDLE hFile = INVALID_HANDLE_VALUE; int iAttempt; DWORD rgAccess[] = { GENERIC_READ | GENERIC_WRITE, // => AL_READWRITE_ACCESS
GENERIC_READ, // => AL_READ_ACCESS
GENERIC_WRITE }; // => AL_WRITE_ACCESS
HANDLE hAccessToken; BOOL fAccessToken = FALSE;
RPC_STATUS rpc_status; DWORD dwStatus; DWORD cbActual; BOOL fImpersonating = FALSE;
BYTE rgbTokenUser[StackBufferSizes]; ULONG cbTokenUser = sizeof(rgbTokenUser); TOKEN_USER *pTokenUser = (TOKEN_USER*) rgbTokenUser;
BYTE rgbTokenGroups[ 4 * StackBufferSizes ]; ULONG cbTokenGroups = sizeof(rgbTokenGroups); TOKEN_GROUPS *pTokenGroups = (TOKEN_GROUPS*) rgbTokenGroups;
BYTE rgbSDAbs[ StackBufferSizes ]; ULONG cbSDAbs = sizeof(rgbSDAbs); PSECURITY_DESCRIPTOR pSDAbs = (PSECURITY_DESCRIPTOR) rgbSDAbs;
BYTE rgbDaclAbs[ StackBufferSizes ]; ULONG cbDaclAbs = sizeof(rgbDaclAbs); PACL pDaclAbs = (PACL) rgbDaclAbs;
BYTE rgbSaclAbs[ StackBufferSizes ]; ULONG cbSaclAbs = sizeof(rgbSaclAbs); PACL pSaclAbs = (PACL) rgbSaclAbs;
BYTE rgbSidOwnerAbs[ StackBufferSizes ]; ULONG cbSidOwnerAbs = sizeof(rgbSidOwnerAbs); PSID pSidOwnerAbs = (PSID) rgbSidOwnerAbs;
BYTE rgbSidGroupAbs[ StackBufferSizes ]; ULONG cbSidGroupAbs = sizeof(rgbSidGroupAbs); PSID pSidGroupAbs = (PSID) rgbSidGroupAbs;
GENERIC_MAPPING Generic_Mapping = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS };
PRIVILEGE_SET rgPrivilegeSet[ 10 ]; ULONG cbPrivilegeSet = sizeof(rgPrivilegeSet); PRIVILEGE_SET *pPrivilegeSet = rgPrivilegeSet;
DWORD dwGrantedAccess; BOOL fAccessStatus;
CSID csidAdministrators; CSecDescriptor csdAdministrators;
PSECURITY_DESCRIPTOR psdCheck = NULL;
// If there is no security descriptor and this isn't an admin share,
// it means that Everyone has 'full control'.
if( NULL == _prgshare_info[ _iCurrentEntry ].shi502_security_descriptor && !_IsAdminShare() ) return( AL_FULL_ACCESS );
// Otherwise, we'll look at the share's DACL ...
__try { // Impersonate the client
TrkAssert( NULL != _IDL_handle );
if( RpcSecurityEnabled() ) { rpc_status = RpcImpersonateClient( _IDL_handle ); if( S_OK != rpc_status ) { TrkLog((TRKDBG_ERROR, TEXT("Couldn't impersonate client"))); TrkRaiseWin32Error( rpc_status ); } fImpersonating = TRUE; } else { if( !ImpersonateSelf( SecurityImpersonation ) ) { TrkLog((TRKDBG_ERROR, TEXT("Couldn't impersonate self"))); TrkRaiseLastError( ); } fImpersonating = TRUE; }
// Get the client's access token
if( !OpenThreadToken( GetCurrentThread(), TOKEN_READ, //TOKEN_ALL_ACCESS,
TRUE, // Open as self
&hAccessToken )) { TrkLog((TRKDBG_ERROR, TEXT("Failed OpenThreadToken"))); TrkRaiseLastError( ); } fAccessToken = TRUE;
// Get the client's owner SID
for( int i = 0; i < 2; i++ ) { if( !GetTokenInformation( hAccessToken, TokenUser, (LPVOID) pTokenUser, cbTokenUser, &cbActual )) { if( i > 0 || ERROR_INSUFFICIENT_BUFFER != GetLastError() ) { TrkLog((TRKDBG_ERROR, TEXT("Failed GetTokenInformation (TokenUser)"))); TrkRaiseLastError( ); }
TrkLog(( TRKDBG_MEND, TEXT("Realloc pTokenUser") )); cbTokenUser = cbActual; pTokenUser = (TOKEN_USER*) new BYTE[ cbTokenUser ]; if( NULL == pTokenUser ) TrkRaiseWin32Error( ERROR_NOT_ENOUGH_MEMORY ); } }
// Get the client's group SID
for( i = 0; i < 2; i++ ) { if( !GetTokenInformation( hAccessToken, TokenGroups, (LPVOID) pTokenGroups, cbTokenGroups, &cbActual )) { if( i > 0 || ERROR_INSUFFICIENT_BUFFER != GetLastError() ) { TrkLog((TRKDBG_ERROR, TEXT("Failed GetTokenInformation (TokenGroups)"))); TrkRaiseLastError( ); }
TrkLog(( TRKDBG_MEND, TEXT("Realloc pTokenGroups") )); cbTokenGroups = cbActual; pTokenGroups = (TOKEN_GROUPS*) new BYTE[ cbTokenGroups ]; if( NULL == pTokenGroups ) TrkRaiseWin32Error( ERROR_NOT_ENOUGH_MEMORY ); } } // Get a pointer to the security descriptor we want to check against.
if( _IsAdminShare() ) { // For admin shares, we don't get a security descriptor in _prgshare_info
// from the NetShareEnum call. But, we know what the ACLs on admin shares
// should be, so we'll craft up an SD.
csidAdministrators.Initialize( CSID::CSID_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS );
csdAdministrators.Initialize(); csdAdministrators.AddAce( CSecDescriptor::ACL_IS_DACL, CSecDescriptor::AT_ACCESS_ALLOWED, FILE_ALL_ACCESS, csidAdministrators );
psdCheck = csdAdministrators;
} else { // Convert the share's Security Descriptor into absolute form.
_AbsoluteSDHelper( _prgshare_info[ _iCurrentEntry ].shi502_security_descriptor, &pSDAbs, &cbSDAbs, &pDaclAbs, &cbDaclAbs, &pSaclAbs, &cbSaclAbs, &pSidOwnerAbs, &cbSidOwnerAbs, &pSidGroupAbs, &cbSidGroupAbs );
psdCheck = pSDAbs; } // The appropriate security descriptor is now in 'psdCheck'
// Put the client's owner SID in the Security Descriptor.
if( !SetSecurityDescriptorOwner( psdCheck, pTokenUser->User.Sid, FALSE )) { TrkLog((TRKDBG_ERROR, TEXT("Couldn't add user to SD"))); TrkRaiseLastError( ); }
// Put the client's group SID in the Security Descriptor.
// (Perf) Why? I think it's because GetEffectiveRightsFromAcl wasn't working,
// so we had to use AccessCheck. But to use that call I think we had to pass
// in an SD with an owner/group, and the one returned from shi502_security_descriptor
// didn't have them.
if( !SetSecurityDescriptorGroup( psdCheck, pTokenGroups->Groups->Sid, FALSE )) { TrkLog((TRKDBG_ERROR, TEXT("Couldn't add group to SD"))); TrkRaiseLastError( ); }
// We have to stop impersonating in order to make the AccessCheck call.
fImpersonating = FALSE; if( RpcSecurityEnabled() ) RpcRevertToSelf(); else RevertToSelf();
// Check the access that this user has to this share. If this returns
// false, it means that NtAccessCheck returned an error. If this returns
// true, but fAccessStatus is false, it means that NtAccessCheck succeeded,
// but it returned an error in its RealStatus parameter. In either case,
// we need to check GetLastError.
for( i = 0; i < 2; i++ ) { if( !AccessCheck( psdCheck, hAccessToken, MAXIMUM_ALLOWED, &Generic_Mapping, pPrivilegeSet, &cbPrivilegeSet, &dwGrantedAccess, &fAccessStatus ) || !fAccessStatus ) { if( i > 0 || ERROR_INSUFFICIENT_BUFFER != GetLastError() ) { TrkLog((TRKDBG_ERROR, TEXT("Couldn't perform AccessCheck for %s (%08x)"), GetShareName(), HRESULT_FROM_WIN32(GetLastError()) )); TrkRaiseLastError( ); }
TrkLog(( TRKDBG_MEND, TEXT("Realloc pPrivilegeSet") )); pPrivilegeSet = (PRIVILEGE_SET*) new BYTE[ cbPrivilegeSet ]; if( NULL == pPrivilegeSet ) { TrkLog(( TRKDBG_ERROR, TEXT("Couldn't alloc pPrivilegeSet") )); TrkRaiseWin32Error( ERROR_NOT_ENOUGH_MEMORY ); } } }
// Reduce this complete list of accesses to a synopsis
if( AreAllAccessesGranted( dwGrantedAccess, FILE_ALL_ACCESS )) AccessLevel = AL_FULL_ACCESS; else if( AreAllAccessesGranted( dwGrantedAccess, FILE_GENERIC_READ | FILE_GENERIC_WRITE )) AccessLevel = AL_READ_WRITE_ACCESS; else if( AreAllAccessesGranted( dwGrantedAccess, FILE_GENERIC_READ )) AccessLevel = AL_READ_ACCESS; else if( AreAllAccessesGranted( dwGrantedAccess, FILE_GENERIC_WRITE )) AccessLevel = AL_WRITE_ACCESS; else AccessLevel = AL_NO_ACCESS;
} __finally {
csdAdministrators.UnInitialize(); csidAdministrators.UnInitialize();
if( fAccessToken ) CloseHandle( hAccessToken );
if( fImpersonating ) { if( RpcSecurityEnabled() ) RpcRevertToSelf(); else RevertToSelf(); }
if( rgPrivilegeSet != pPrivilegeSet ) delete[] pPrivilegeSet;
if( rgbTokenUser != (BYTE*) pTokenUser ) delete[] pTokenUser; if( rgbTokenGroups != (BYTE*) pTokenGroups ) delete[] pTokenGroups;
if( rgbSDAbs != (BYTE*) pSDAbs ) delete[] pSDAbs; if( rgbDaclAbs != (BYTE*) pDaclAbs ) delete[] pDaclAbs; if( rgbSaclAbs != (BYTE*) pSaclAbs ) delete[] pSaclAbs; if( rgbSidOwnerAbs != (BYTE*) pSidOwnerAbs ) delete[] pSidOwnerAbs; if( rgbSidGroupAbs != (BYTE*) pSidGroupAbs ) delete[] pSidGroupAbs;
}
return( AccessLevel );
}
//+-------------------------------------------------------------------
//
// Function: CShareEnumerator::GenerateUNCPath
//
// Synopsis: Generate a UNC path to the give drive-based path
// WRT to the current share.
//
// Arguments: [ptszUNCPath] (out)
// Filled with the generated UNC path. This
// is assumed to be at least MAX_PATH+1 characters.
//
// [ptszDrivePath] (in)
// The drive-based path to the file.
// E.g. "c:\my documents\wordfile.doc".
//
// Returns: FALSE if the current share doesn't cover the
// file, TRUE otherwise.
//
// Raises: On error.
//
//--------------------------------------------------------------------
BOOL CShareEnumerator::GenerateUNCPath( TCHAR *ptszUNCPath, const TCHAR * ptszDrivePath ) { TrkAssert( _fInitialized ); TrkAssert( TEXT(':') == ptszDrivePath[1] );
// Ensure that this share covers the drive-based path.
if( !CoversDrivePath( ptszDrivePath )) return( FALSE );
// Start out the UNC name with the \\machine\share.
_tcscpy( ptszUNCPath, _tszMachineName ); _tcscat( ptszUNCPath, TEXT("\\") ); _tcscat( ptszUNCPath, GetShareName() ); _tcscat( ptszUNCPath, TEXT("\\") );
// Finish the UNC name with the portion of the
// volume path which is under the share path.
_tcscat( ptszUNCPath, &ptszDrivePath[ QueryCCHSharePath() ] );
return( TRUE ); }
|