从具有凭据的远程非受信任域访问共享文件(UNC)

时间:2009-03-18 16:25:45

标签: c# windows unc file-sharing

我们遇到了一个需要解决的有趣情况,我的搜索已经发现了。因此,我呼吁SO社区寻求帮助。

问题是:我们需要以编程方式访问不在我们域中的共享文件,并且不通过远程文件共享/ UNC在受信任的外部域中。当然,我们需要为远程计算机提供凭据。

通常,人们可以通过以下两种方式之一解决这个问题:

  1. 将文件共享映射为驱动器,并在那时提供凭据。这通常使用NET USE命令或复制NET USE的Win32函数来完成。
  2. 使用UNC路径访问文件,就好像远程计算机位于域中一样,并确保作为本地用户在远程计算机上复制运行程序的帐户(包括密码)。基本上利用了当用户尝试访问共享文件时Windows将自动提供当前用户凭据的事实。
  3. 不要使用远程文件共享。使用FTP(或其他方法)传输文件,在本地处理它,然后将其传回。
  4. 出于各种各样的原因,我们的安全/网络架构师拒绝了前两种方法。第二种方法显然是一个安全漏洞;如果远程计算机受到危害,则本地计算机现在处于危险之中。第一种方法是不能令人满意的,因为新安装的驱动器是在程序访问文件期间本地计算机上的其他程序可用的共享资源。即使这很可能使这个暂时存在,但他们认为这仍然是一个漏洞。

    他们对第三个选项开放,但远程网络管理员坚持使用SFTP而不是FTPS,而FtpWebRequest仅支持FTPS。 SFTP 是更适合防火墙的选项,并且我可以使用几个库来实现该方法,但如果可以的话,我宁愿减少依赖项。

    我在MSDN上搜索了使用远程文件共享的托管或win32方法,但我没有想出任何有用的东西。

    所以我问:还有另外一种方法吗?我是否错过了一个超级秘密的win32功能,可以满足我的需求?或者我必须追求选项3的一些变体吗?

10 个答案:

答案 0 :(得分:162)

解决问题的方法是使用名为WNetUseConnection的Win32 API 使用此功能通过身份验证连接到UNC路径,而不是映射驱动器

这将允许您连接到远程计算机,即使它不在同一个域上,即使它具有不同的用户名和密码。

使用WNetUseConnection后,您将能够通过UNC路径访问该文件,就像您在同一个域中一样。最好的方法可能是通过行政内置股票 示例:\\ computername \ c $ \ program files \ Folder \ file.txt

Here is some sample C# code that uses WNetUseConnection

注意,对于NetResource,您应该为lpLocalName和lpProvider传递null。 dwType应为RESOURCETYPE_DISK。 lpRemoteName应为\\ ComputerName。

答案 1 :(得分:111)

对于寻求快速解决方案的人,您可以使用我最近写的NetworkShareAccesser(基于this answer(非常感谢!)):

<强>用法:

using (NetworkShareAccesser.Access(REMOTE_COMPUTER_NAME, DOMAIN, USER_NAME, PASSWORD))
{
    File.Copy(@"C:\Some\File\To\copy.txt", @"\\REMOTE-COMPUTER\My\Shared\Target\file.txt");
}

警告:请务必确保调用Dispose的{​​{1}}(即使应用崩溃!),否则Windows上将保留打开的连接。您可以打开NetworkShareAccesser提示并输入cmd

来查看所有未结连接

守则:

net use

答案 2 :(得分:16)

AFAIK,您无需映射 UNC路径到驱动器号,以便为服务器建立凭据。我经常使用批处理脚本,如:

net use \\myserver /user:username password

:: do something with \\myserver\the\file\i\want.xml

net use /delete \\my.server.com

但是,与您的程序在同一帐户上运行的任何程序仍然可以访问username:password有权访问的所有内容。一种可能的解决方案是将您的程序隔离在自己的本地用户帐户中(UNC访问权限是调用NET USE帐户的本地访问权限。)

注意:使用SMB跨域并不是很好地利用IMO这项技术。如果安全性非常重要,那么SMB缺乏加密这一事实本身就是一种阻碍。

答案 3 :(得分:4)

虽然我不了解自己,但我当然希望#2不正确......我想我认为Windows不会自动发出我的登录信息(至少我的密码!)任何机器,更不用说不属于我信任的机器了。

无论如何,你有没有探索过模仿架构?您的代码看起来与此类似:

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

    context.Undo();
}

在这种情况下,token变量是IntPtr。要获取此变量的值,您必须调用非托管的LogonUser Windows API函数。快速前往pinvoke.net会给我们以下签名:

[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
);

用户名,域名和密码看起来应该很明显。查看可以传递给dwLogonType和dwLogonProvider的各种值,以确定最适合您需求的值。

此代码尚未经过测试,因为我在此处没有第二个可以验证的域名,但这应该会让您走上正轨。

答案 4 :(得分:4)

我建议使用NetUseAdd而不是WNetUseConnection。 WNetUseConnection是WNetUseConnection2和WNetUseConnection3取代的遗留功能,但所有这些功能都创建了一个在Windows资源管理器中可见的网络设备。 NetUseAdd相当于在DOS提示符下调用net use以在远程计算机上进行身份验证。

如果您调用NetUseAdd,则后续尝试访问该目录应该会成功。

答案 5 :(得分:2)

大多数SFTP服务器也支持SCP,这可以更容易地找到库。您甚至可以从代码中调用现有客户端,例如PuTTY附带的pscp。

如果您正在使用的文件类型很简单,就像文本或XML文件一样,您甚至可以编写自己的客户端/服务器实现来使用.NET Remoting或Web等操作来处理文件服务。

答案 6 :(得分:1)

我已经看到选项3以JScape tools以非常简单的方式实现了。你可以尝试一下。它不是免费的,但它确实起作用。

答案 7 :(得分:0)

这里有一个最小的POC类w /所有被删除的

adb shell

您可以直接使用using System; using System.ComponentModel; using System.Runtime.InteropServices; public class UncShareWithCredentials : IDisposable { private string _uncShare; public UncShareWithCredentials(string uncShare, string userName, string password) { var nr = new Native.NETRESOURCE { dwType = Native.RESOURCETYPE_DISK, lpRemoteName = uncShare }; int result = Native.WNetUseConnection(IntPtr.Zero, nr, password, userName, 0, null, null, null); if (result != Native.NO_ERROR) { throw new Win32Exception(result); } _uncShare = uncShare; } public void Dispose() { if (!string.IsNullOrEmpty(_uncShare)) { Native.WNetCancelConnection2(_uncShare, Native.CONNECT_UPDATE_PROFILE, false); _uncShare = null; } } private class Native { public const int RESOURCETYPE_DISK = 0x00000001; public const int CONNECT_UPDATE_PROFILE = 0x00000001; public const int NO_ERROR = 0; [DllImport("mpr.dll")] public static extern int WNetUseConnection(IntPtr hwndOwner, NETRESOURCE lpNetResource, string lpPassword, string lpUserID, int dwFlags, string lpAccessName, string lpBufferSize, string lpResult); [DllImport("mpr.dll")] public static extern int WNetCancelConnection2(string lpName, int dwFlags, bool fForce); [StructLayout(LayoutKind.Sequential)] public class NETRESOURCE { public int dwScope; public int dwType; public int dwDisplayType; public int dwUsage; public string lpLocalName; public string lpRemoteName; public string lpComment; public string lpProvider; } } } w / \\server\share\folder,无需事先将其剥离到WNetUseConnection部分。

答案 8 :(得分:0)

根据brian参考附加我的 vb.net代码

Imports System.ComponentModel

Imports System.Runtime.InteropServices

Public Class PinvokeWindowsNetworking

Const NO_ERROR As Integer = 0



Private Structure ErrorClass

    Public num As Integer

    Public message As String



    Public Sub New(ByVal num As Integer, ByVal message As String)

        Me.num = num

        Me.message = message

    End Sub

End Structure



Private Shared ERROR_LIST As ErrorClass() = New ErrorClass() {

    New ErrorClass(5, "Error: Access Denied"),

    New ErrorClass(85, "Error: Already Assigned"),

    New ErrorClass(1200, "Error: Bad Device"),

    New ErrorClass(67, "Error: Bad Net Name"),

    New ErrorClass(1204, "Error: Bad Provider"),

    New ErrorClass(1223, "Error: Cancelled"),

    New ErrorClass(1208, "Error: Extended Error"),

    New ErrorClass(487, "Error: Invalid Address"),

    New ErrorClass(87, "Error: Invalid Parameter"),

    New ErrorClass(1216, "Error: Invalid Password"),

    New ErrorClass(234, "Error: More Data"),

    New ErrorClass(259, "Error: No More Items"),

    New ErrorClass(1203, "Error: No Net Or Bad Path"),

    New ErrorClass(1222, "Error: No Network"),

    New ErrorClass(1206, "Error: Bad Profile"),

    New ErrorClass(1205, "Error: Cannot Open Profile"),

    New ErrorClass(2404, "Error: Device In Use"),

    New ErrorClass(2250, "Error: Not Connected"),

    New ErrorClass(2401, "Error: Open Files")}



Private Shared Function getErrorForNumber(ByVal errNum As Integer) As String

    For Each er As ErrorClass In ERROR_LIST

        If er.num = errNum Then Return er.message

    Next



    Try

        Throw New Win32Exception(errNum)

    Catch ex As Exception

        Return "Error: Unknown, " & errNum & " " & ex.Message

    End Try



    Return "Error: Unknown, " & errNum

End Function



<DllImport("Mpr.dll")>

Private Shared Function WNetUseConnection(ByVal hwndOwner As IntPtr, ByVal lpNetResource As NETRESOURCE, ByVal lpPassword As String, ByVal lpUserID As String, ByVal dwFlags As Integer, ByVal lpAccessName As String, ByVal lpBufferSize As String, ByVal lpResult As String) As Integer

End Function



<DllImport("Mpr.dll")>

Private Shared Function WNetCancelConnection2(ByVal lpName As String, ByVal dwFlags As Integer, ByVal fForce As Boolean) As Integer

End Function



<StructLayout(LayoutKind.Sequential)>

Private Class NETRESOURCE

    Public dwScope As Integer = 0

    Public dwType As Integer = 0

    Public dwDisplayType As Integer = 0

    Public dwUsage As Integer = 0

    Public lpLocalName As String = ""

    Public lpRemoteName As String = ""

    Public lpComment As String = ""

    Public lpProvider As String = ""

End Class



Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String) As String

    Return connectToRemote(remoteUNC, username, password, False)

End Function



Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String, ByVal promptUser As Boolean) As String

    Dim nr As NETRESOURCE = New NETRESOURCE()

    nr.dwType = ResourceTypes.Disk

    nr.lpRemoteName = remoteUNC

    Dim ret As Integer



    If promptUser Then

        ret = WNetUseConnection(IntPtr.Zero, nr, "", "", Connects.Interactive Or Connects.Prompt, Nothing, Nothing, Nothing)

    Else

        ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, Nothing, Nothing, Nothing)

    End If



    If ret = NO_ERROR Then Return Nothing

    Return getErrorForNumber(ret)

End Function



Public Shared Function disconnectRemote(ByVal remoteUNC As String) As String

    Dim ret As Integer = WNetCancelConnection2(remoteUNC, Connects.UpdateProfile, False)

    If ret = NO_ERROR Then Return Nothing

    Return getErrorForNumber(ret)

End Function


Enum Resources As Integer

    Connected = &H1

    GlobalNet = &H2

    Remembered = &H3

End Enum


Enum ResourceTypes As Integer

    Any = &H0

    Disk = &H1

    Print = &H2

End Enum


Enum ResourceDisplayTypes As Integer

    Generic = &H0

    Domain = &H1

    Server = &H2

    Share = &H3

    File = &H4

    Group = &H5

End Enum


Enum ResourceUsages As Integer

    Connectable = &H1

    Container = &H2

End Enum


Enum Connects As Integer

    Interactive = &H8

    Prompt = &H10

    Redirect = &H80

    UpdateProfile = &H1

    CommandLine = &H800

    CmdSaveCred = &H1000

    LocalDrive = &H100

End Enum


End Class

使用方法

Dim login = PinvokeWindowsNetworking.connectToRemote("\\ComputerName", "ComputerName\UserName", "Password")

    If IsNothing(login) Then



        'do your thing on the shared folder



       PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")

    End If

答案 9 :(得分:-2)

我向MS寻找答案。第一个解决方案假定运行应用程序进程的用户帐户可以访问共享文件夹或驱动器(相同域)。确保您的DNS已解析或尝试使用IP地址。只需执行以下操作:

 DirectoryInfo di = new DirectoryInfo(PATH);
 var files = di.EnumerateFiles("*.*", SearchOption.AllDirectories);

如果您想跨越不同的域,带有凭据的.NET 2.0遵循以下模型:

WebRequest req = FileWebRequest.Create(new Uri(@"\\<server Name>\Dir\test.txt"));

        req.Credentials = new NetworkCredential(@"<Domain>\<User>", "<Password>");
        req.PreAuthenticate = true;

        WebResponse d = req.GetResponse();
        FileStream fs = File.Create("test.txt");

        // here you can check that the cast was successful if you want. 
        fs = d.GetResponseStream() as FileStream;
        fs.Close();