Доступ к общему файлу (UNC) из удаленного недоверенного домена с учетными данными - PullRequest
142 голосов
/ 18 марта 2009

Мы столкнулись с интересной ситуацией, которая требует разрешения, и мои поиски закончились ничем. Поэтому я обращаюсь к сообществу SO за помощью.

Проблема заключается в следующем: нам необходим программный доступ к общему файлу, который не находится в нашем домене и не находится в доверенном внешнем домене через удаленный общий доступ к файлам / UNC. Естественно, нам нужно предоставить учетные данные для удаленного компьютера.

Как правило, эту проблему можно решить одним из двух способов:

  1. Сопоставьте общий файловый ресурс как диск и укажите учетные данные в то время. Обычно это делается с помощью команды NET USE или функций Win32, которые дублируют NET USE.
  2. Получите доступ к файлу с UNC-путем, как если бы удаленный компьютер находился в домене, и убедитесь, что учетная запись, под которой запускается программа, дублируется (включая пароль) на удаленном компьютере как локальный пользователь. В основном используется тот факт, что Windows автоматически предоставит учетные данные текущего пользователя, когда пользователь попытается получить доступ к общему файлу.
  3. Не использовать удаленный обмен файлами. Используйте FTP (или другие средства) для передачи файла, работайте с ним локально, а затем передайте его обратно.

По разным и разным причинам наши архитекторы безопасности / сети отвергли первые два подхода. Второй подход, очевидно, является дырой в безопасности; если удаленный компьютер скомпрометирован, локальный компьютер теперь находится в опасности. Первый подход неудовлетворителен, поскольку вновь смонтированный диск является общим ресурсом, доступным для других программ на локальном компьютере во время доступа программы к файлам. Несмотря на то, что это вполне возможно сделать временным, по их мнению, это все еще дыра.

Они открыты для третьего варианта, но администраторы удаленных сетей настаивают на SFTP, а не на FTPS, а FtpWebRequest поддерживает только FTPS. SFTP является более дружественным к брандмауэру вариантом, и есть несколько библиотек, которые я мог бы использовать для этого подхода, но я бы предпочел уменьшить свои зависимости, если смогу.

Я искал в MSDN управляемый или win32 способ использования удаленного обмена файлами, но мне не удалось найти ничего полезного.

И поэтому я спрашиваю: есть ли другой путь? Я пропустил сверхсекретную функцию win32, которая делает то, что я хочу? Или я должен использовать какой-то вариант 3?

Ответы [ 10 ]

163 голосов
/ 26 марта 2009

Способ решения вашей проблемы - использовать Win32 API с именем WNetUseConnection .
Используйте эту функцию для подключения к пути UNC с аутентификацией, НЕ для подключения диска .

Это позволит вам подключиться к удаленному компьютеру, даже если он не находится в одном домене, и даже если у него другое имя пользователя и пароль.

Как только вы используете WNetUseConnection, вы сможете получить доступ к файлу по UNC-пути, как если бы вы были в том же домене. Лучший способ, вероятно, через административные встроенные акции.
Пример: \\ имя_компьютера \ c $ \ program files \ Folder \ file.txt

Вот пример кода C #, который использует WNetUseConnection .

Обратите внимание, что для NetResource вы должны передать null для lpLocalName и lpProvider. Тип dwType должен быть RESOURCETYPE_DISK. LpRemoteName должно быть \\ ComputerName.

111 голосов
/ 14 февраля 2013

Для людей, которые ищут быстрое решение, вы можете использовать NetworkShareAccesser, который я недавно написал (на основе этот ответ (большое спасибо!)):

Использование:

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 из NetworkShareAccesser вызывается (даже если ваше приложение вылетает!), В противном случае в Windows останется открытое соединение. Вы можете увидеть все открытые соединения, открыв приглашение cmd и введя net use.

Код:

/// <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: domainOrComputername\Username) 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);
    }
}
16 голосов
/ 24 марта 2009

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 не имеет шифрования, сам по себе является небольшим препятствием.

4 голосов
/ 26 марта 2009

Вместо WNetUseConnection я бы рекомендовал NetUseAdd . WNetUseConnection - это устаревшая функция, которая была заменена WNetUseConnection2 и WNetUseConnection3, но все эти функции создают сетевое устройство, которое отображается в проводнике Windows. NetUseAdd является эквивалентом вызова net use в командной строке DOS для аутентификации на удаленном компьютере.

Если вы вызываете NetUseAdd, то последующие попытки доступа к каталогу должны быть успешными.

4 голосов
/ 24 марта 2009

Хотя я сам не знаю, я, конечно, надеюсь, что № 2 неверен ... Я хотел бы думать, что Windows не собирается АВТОМАТИЧЕСКИ выдавать мою регистрационную информацию (меньше всего мой пароль!) к любой машине, не говоря уже о той, которая не является частью моего доверия.

Независимо от того, вы исследовали архитектуру олицетворения? Ваш код будет выглядеть примерно так:

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

    context.Undo();
}

В этом случае переменная token является IntPtr. Чтобы получить значение для этой переменной, вам нужно вызвать неуправляемую функцию Windows API LogonUser. Быстрый переход на 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, чтобы определить, какое из них лучше всего соответствует вашим потребностям.

Этот код не был протестирован, так как у меня нет второго домена, где я мог бы проверить, но это, мы надеемся, должно поставить вас на правильный путь.

2 голосов
/ 18 марта 2009

Большинство SFTP-серверов также поддерживают SCP, что облегчает поиск библиотек. Вы даже можете просто вызвать существующий клиент из своего кода, например pscp, включенного в PuTTY .

Если тип файла, с которым вы работаете, является чем-то простым, например, текстовым или XML-файлом, вы можете даже пойти так далеко, чтобы написать собственную реализацию клиент / сервер для манипулирования файлом с использованием чего-то вроде .NET Remoting или web услуги.

1 голос
/ 26 марта 2009

Я видел вариант 3, реализованный с помощью инструментов JScape довольно простым способом. Вы можете попробовать. Это не бесплатно, но делает свою работу.

0 голосов
/ 30 декабря 2018

я прикрепляю мой код 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
0 голосов
/ 09 января 2018

Здесь минимальный класс POC со всеми удаленными фуфлами

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;
        }
    }
}

Вы можете напрямую использовать \\server\share\folder w / WNetUseConnection, нет необходимости разбивать его на \\server часть только заранее.

0 голосов
/ 27 октября 2015

Я посмотрел в 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();
...