Accessing a Shared File (UNC) From a Remote, Non

We've run into an interesting situation that needs solving, and my searches have turned up nill. I therefore appeal to the SO community for help.

The issue is this: we have a need to programmatically access a shared file that is not in our domain, and is not within a trusted external domain via remote file sharing / UNC. Naturally, we need to supply credentials to the remote machine.

Typically, one solves this problem in one of two ways:

  • Map the file share as a drive and supply the credentials at that time. This is typically done using the NET USE command or the Win32 functions that duplicate NET USE .
  • Access the file with a UNC path as if the remote computer were on the domain and ensure that the account under which the program runs is duplicated (including password) on the remote machine as a local user. Basically leverage the fact that Windows will automatically supply the current user's credentials when the user attempts to access a shared file.
  • Don't use remote file sharing. Use FTP (or some other means) to transfer the file, work on it locally, then transfer it back.
  • For various and sundry reasons, our security / network architects have rejected the first two approaches. The second approach is obviously a security hole; if the remote computer is compromised, the local computer is now at risk. The first approach is unsatisfactory because the newly mounted drive is a shared resource available to other programs on the local computer during file access by the program. Even though it's quite possible to make this temporary, it's still a hole in their opinion.

    They're open to the third option, but the remote network admins insist on SFTP rather than FTPS, and FtpWebRequest only supports FTPS. SFTP is the more firewall-friendly option and there are a couple libraries I could use for that approach, but I'd prefer to reduce my dependencies if I can.

    I've searched MSDN for either a managed or a win32 means of using remote file sharing, but I have failed to come up with anything useful.

    And so I ask: Is there another way? Did I miss a super-secret win32 function that does what I want? Or must I pursue some variant of option 3?


    The way to solve your problem is to use a Win32 API called WNetUseConnection.
    Use this function to connect to a UNC path with authentication, NOT to map a drive .

    This will allow you to connect to a remote machine, even if it is not on the same domain, and even if it has a different username and password.

    Once you have used WNetUseConnection you will be able to access the file via a UNC path as if you were on the same domain. The best way is probably through the administrative built in shares.
    Example: computernamec$program filesFolderfile.txt

    Here is some sample C# code that uses WNetUseConnection.

    Note, for the NetResource, you should pass null for the lpLocalName and lpProvider. The dwType should be RESOURCETYPE_DISK. The lpRemoteName should be ComputerName.


    For people looking for a quick solution, you can use the NetworkShareAccesser I wrote recently (based on this answer (thanks so much!)):

    Usage:

    using (NetworkShareAccesser.Access(REMOTE_COMPUTER_NAME, DOMAIN, USER_NAME, PASSWORD))
    {
        File.Copy(@"C:SomeFileTocopy.txt", @"REMOTE-COMPUTERMySharedTargetfile.txt");
    }
    

    WARNING: Please make absolutely sure, that Dispose of the NetworkShareAccesser is called (even if you app crashes!), otherwise an open connection will remain on Windows. You can see all open connections by opening the cmd prompt and enter net use .

    The Code:

    /// <summary>
    /// Provides access to a network share.
    /// </summary>
    public class NetworkShareAccesser : IDisposable
    {
        private string _remoteUncName;
        private string _remoteComputerName;
    
        public string RemoteComputerName
        {
            get
            {
                return this._remoteComputerName;
            }
            set
            {
                this._remoteComputerName = value;
                this._remoteUncName = @"" + this._remoteComputerName;
            }
        }
    
        public string UserName
        {
            get;
            set;
        }
        public string Password
        {
            get;
            set;
        }
    
        #region Consts
    
        private const int RESOURCE_CONNECTED = 0x00000001;
        private const int RESOURCE_GLOBALNET = 0x00000002;
        private const int RESOURCE_REMEMBERED = 0x00000003;
    
        private const int RESOURCETYPE_ANY = 0x00000000;
        private const int RESOURCETYPE_DISK = 0x00000001;
        private const int RESOURCETYPE_PRINT = 0x00000002;
    
        private const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
        private const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
        private const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
        private const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
        private const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
        private const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;
    
        private const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
        private const int RESOURCEUSAGE_CONTAINER = 0x00000002;
    
    
        private const int CONNECT_INTERACTIVE = 0x00000008;
        private const int CONNECT_PROMPT = 0x00000010;
        private const int CONNECT_REDIRECT = 0x00000080;
        private const int CONNECT_UPDATE_PROFILE = 0x00000001;
        private const int CONNECT_COMMANDLINE = 0x00000800;
        private const int CONNECT_CMD_SAVECRED = 0x00001000;
    
        private const int CONNECT_LOCALDRIVE = 0x00000100;
    
        #endregion
    
        #region Errors
    
        private const int NO_ERROR = 0;
    
        private const int ERROR_ACCESS_DENIED = 5;
        private const int ERROR_ALREADY_ASSIGNED = 85;
        private const int ERROR_BAD_DEVICE = 1200;
        private const int ERROR_BAD_NET_NAME = 67;
        private const int ERROR_BAD_PROVIDER = 1204;
        private const int ERROR_CANCELLED = 1223;
        private const int ERROR_EXTENDED_ERROR = 1208;
        private const int ERROR_INVALID_ADDRESS = 487;
        private const int ERROR_INVALID_PARAMETER = 87;
        private const int ERROR_INVALID_PASSWORD = 1216;
        private const int ERROR_MORE_DATA = 234;
        private const int ERROR_NO_MORE_ITEMS = 259;
        private const int ERROR_NO_NET_OR_BAD_PATH = 1203;
        private const int ERROR_NO_NETWORK = 1222;
    
        private const int ERROR_BAD_PROFILE = 1206;
        private const int ERROR_CANNOT_OPEN_PROFILE = 1205;
        private const int ERROR_DEVICE_IN_USE = 2404;
        private const int ERROR_NOT_CONNECTED = 2250;
        private const int ERROR_OPEN_FILES = 2401;
    
        #endregion
    
        #region PInvoke Signatures
    
        [DllImport("Mpr.dll")]
        private static extern int WNetUseConnection(
            IntPtr hwndOwner,
            NETRESOURCE lpNetResource,
            string lpPassword,
            string lpUserID,
            int dwFlags,
            string lpAccessName,
            string lpBufferSize,
            string lpResult
            );
    
        [DllImport("Mpr.dll")]
        private static extern int WNetCancelConnection2(
            string lpName,
            int dwFlags,
            bool fForce
            );
    
        [StructLayout(LayoutKind.Sequential)]
        private class NETRESOURCE
        {
            public int dwScope = 0;
            public int dwType = 0;
            public int dwDisplayType = 0;
            public int dwUsage = 0;
            public string lpLocalName = "";
            public string lpRemoteName = "";
            public string lpComment = "";
            public string lpProvider = "";
        }
    
        #endregion
    
        /// <summary>
        /// Creates a NetworkShareAccesser for the given computer name. The user will be promted to enter credentials
        /// </summary>
        /// <param name="remoteComputerName"></param>
        /// <returns></returns>
        public static NetworkShareAccesser Access(string remoteComputerName)
        {
            return new NetworkShareAccesser(remoteComputerName);
        }
    
        /// <summary>
        /// Creates a NetworkShareAccesser for the given computer name using the given domain/computer name, username and password
        /// </summary>
        /// <param name="remoteComputerName"></param>
        /// <param name="domainOrComuterName"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        public static NetworkShareAccesser Access(string remoteComputerName, string domainOrComuterName, string userName, string password)
        {
            return new NetworkShareAccesser(remoteComputerName,
                                            domainOrComuterName + @"" + userName,
                                            password);
        }
    
        /// <summary>
        /// Creates a NetworkShareAccesser for the given computer name using the given username (format: domainOrComputernameUsername) and password
        /// </summary>
        /// <param name="remoteComputerName"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        public static NetworkShareAccesser Access(string remoteComputerName, string userName, string password)
        {
            return new NetworkShareAccesser(remoteComputerName, 
                                            userName,
                                            password);
        }
    
        private NetworkShareAccesser(string remoteComputerName)
        {
            RemoteComputerName = remoteComputerName;               
    
            this.ConnectToShare(this._remoteUncName, null, null, true);
        }
    
        private NetworkShareAccesser(string remoteComputerName, string userName, string password)
        {
            RemoteComputerName = remoteComputerName;
            UserName = userName;
            Password = password;
    
            this.ConnectToShare(this._remoteUncName, this.UserName, this.Password, false);
        }
    
        private void ConnectToShare(string remoteUnc, string username, string password, bool promptUser)
        {
            NETRESOURCE nr = new NETRESOURCE
            {
                dwType = RESOURCETYPE_DISK,
                lpRemoteName = remoteUnc
            };
    
            int result;
            if (promptUser)
            {
                result = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
            }
            else
            {
                result = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);
            }
    
            if (result != NO_ERROR)
            {
                throw new Win32Exception(result);
            }
        }
    
        private void DisconnectFromShare(string remoteUnc)
        {
            int result = WNetCancelConnection2(remoteUnc, CONNECT_UPDATE_PROFILE, false);
            if (result != NO_ERROR)
            {
                throw new Win32Exception(result);
            }
        }
    
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            this.DisconnectFromShare(this._remoteUncName);
        }
    }
    

    AFAIK, you don't need to map the UNC path to a drive letter in order to establish credentials for a server. I regularly used batch scripts like:

    net use myserver /user:username password
    
    :: do something with myserverthefileiwant.xml
    
    net use /delete my.server.com
    

    However, any program running on the same account as your program would still be able to access everything that username:password has access to. A possible solution could be to isolate your program in its own local user account (the UNC access is local to the account that called NET USE ).

    Note: Using SMB accross domains is not quite a good use of the technology, IMO. If security is that important, the fact that SMB lacks encryption is a bit of a damper all by itself.

    链接地址: http://www.djcxy.com/p/7680.html

    上一篇: 如何在Visual Studio 2010中使用Boost

    下一篇: 从远程访问共享文件(UNC),Non