views:

5453

answers:

6

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:

  1. 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.
  2. 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.
  3. 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?

+1  A: 

Most SFTP servers support SCP as well which can be a lot easier to find libraries for. You could even just call an existing client from your code like pscp included with PuTTY.

If the type of file you're working with is something simple like a text or XML file, you could even go so far as to write your own client/server implementation to manipulate the file using something like .NET Remoting or web services.

Ryan
+3  A: 

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 \\myserver\the\file\i\want.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.

Jacob
If you're correct about the UNC access being only available to the account that called `NET USE`, that might be a viable approach. Are you certain we need to use a local account, however? Wouldn't the `NET USE` call be local to the machine on which it was called? You've given me a good research path
Randolpho
AFAIK, and I may be wrong, the UNC access will only be available to the specific security principal (SAM account, whatever) under which the call to NET USE was made. You can verify this by using RunAs to map the path and then trying to access it from another account.
Jacob
in my case, i had to use net use \\myserver /user:username@domain passwordas the user is on a different domain.
StarCub
+1  A: 

While I don't know myself, I would certainly hope that #2 is incorrect...I'd like to think that Windows isn't going to AUTOMATICALLY give out my login information (least of all my password!) to any machine, let alone one that isn't part of my trust.

Regardless, have you explored the impersonation architecture? Your code is going to look similar to this:

using (System.Security.Principal.WindowsImpersonationContext context = System.Security.Principal.WindowsIdentity.Impersonate(token))
{
    // Do network operations here

    context.Undo();
}

In this case, the token variable is an IntPtr. In order to get a value for this variable, you'll have to call the unmanaged LogonUser Windows API function. A quick trip to pinvoke.net gives us the following signature:

[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
    string lpszUsername,
    string lpszDomain,
    string lpszPassword,
    int dwLogonType,
    int dwLogonProvider,
    out IntPtr phToken
);

Username, domain, and password should seem fairly obvious. Have a look at the various values that can be passed to dwLogonType and dwLogonProvider to determine the one that best suits your needs.

This code hasn't been tested, as I don't have a second domain here where I can verify, but this should hopefully put you on the right track.

Good luck!

Adam Robinson
Impersonation won't work when you are trying to use a login id from an untrusted domain. The user id has to be able to log on locally.
Moose
Yeah, we tried this route, it ended up being as @Moose says: The domain is untrusted and therefore impersonation won't work.
Randolpho
Yeah, once I saw that comment that's why I posted the answer using NetUseAdd (the primary difference between it and the WNetUseConnection and WNetAddConnection functions being that NetUseAdd does not make the connection visible in Windows Explorer).
Adam Robinson
+21  A: 

The way to solve your problem is to use a Win32 API called WNetUseConnection.
Use this function to connect to a UNC path with authenticaiton, 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: \\computername\c$\program files\Folder\file.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.

Brian R. Bondy
You certainly didn't get a negative vote from me. I shall research this. Thanks!
Randolpho
Thanks @Randolpho, I'm confident this will work for you, I've done this many times for UNC access to unrelated network machines.
Brian R. Bondy
I take it this does the same as my answer, only directly via the Win32 API? In that case, this is a muuuuch better option.
Jacob
@Jacob: I'm not sure, but I think this function is more flexible, and you can use the related WNet API functions too.
Brian R. Bondy
Is there any way to use functions like these to explicitly open/close connections to a network machine using the current credentials, i.e., without providing the username and password? I am specifically interested in closing a connection after accessing a file share.
flipdoubt
Not for connecting, unless the computer itself doesn't have a username or password. For disconnecting sure you can. You can even do it via command line instead.
Brian R. Bondy
Hi Brian. The docs you link to say you can pass NULL for the user name and password to use the current credentials. I will do some testing to see if this works.
flipdoubt
Passing null for the user name/password allows me to connect, but how can I prove that I have disconnected? Is there something on the server I can look at? On Server 2003, I can watch the sessions, but the list of current sessions updates just as fast when my app does not use these APIs.
flipdoubt
+1  A: 

I've seen option 3 implemented with JScape tools in a pretty straightforward fashion. You might give it a try. It's not free, but it does its job.

DreamSonic
+1  A: 

Rather than WNetUseConnection, I would recommend NetUseAdd. WNetUseConnection is a legacy function that's been superceded by WNetUseConnection2 and WNetUseConnection3, but all of those functions create a network device that's visible in Windows Explorer. NetUseAdd is the equivalent of calling net use in a DOS prompt to authenticate on a remote computer.

If you call NetUseAdd then subsequent attempts to access the directory should succeed.

Adam Robinson
@Adam Robinson: THis is not true. There is no such WNetUseConnection2 nor WNetUseConnection3. I think you are thinkign about WNetAddConnection being superceded by WNetAddConnection2 and WnetAddConnection3. Also the information you gave about it is not true.
Brian R. Bondy
WNetUseConnection is like WNetAddConnection3, but it also has an optional ability to create a mapped local drive. Which you don't have to use.
Brian R. Bondy