Der Zugriff auf eine Shared File (UNC) von einer entfernten, nicht vertrauenswürdiger Domäne mit Credentials

StackOverflow https://stackoverflow.com/questions/659013

  •  20-08-2019
  •  | 
  •  

Frage

Wir haben in eine interessante Situation ausführen, die Bedürfnisse zu lösen, und meine Suche hat nill auftaucht. Deshalb appelliere ich an die SO Gemeinschaft um Hilfe.

Das Problem ist dies: wir haben das Bedürfnis programmatisch eine freigegebene Datei zuzugreifen, die nicht in unserer Domäne ist, und nicht innerhalb einer vertrauenswürdigen externen Domäne über Remote-File-Sharing / UNC. Natürlich müssen wir Anmeldeinformationen an den Remote-Computer liefern.

Normalerweise löst man dieses Problem in einer von zwei Möglichkeiten:

  1. Ordnen Sie die Dateifreigabe als Laufwerk und geben Sie die Anmeldeinformationen zu dieser Zeit. Dies wird in der Regel mit dem NET USE Befehl oder die Win32-Funktionen durchgeführt, die NET USE duplizieren.
  2. Rufen Sie die Datei mit einem UNC-Pfad, als ob der Remote-Computer in der Domäne war und sicherzustellen, dass das Konto, unter dem das Programm läuft dupliziert (einschließlich Passwort) auf dem entfernten Rechner als lokalen Benutzer. Grundsätzlich nutzt die Tatsache, dass Windows automatisch die aktuellen Benutzer der Anmeldeinformationen liefern, wenn der Benutzer eine freigegebene Datei zuzugreifen versucht.
  3. Verwenden Sie keine Remote-Dateifreigabe. Verwenden Sie FTP (oder andere Mittel), um die Datei zu übertragen, die Arbeit an lokal, dann Transfer zurück.

Für verschiedene und verschiedene Gründe, unsere Sicherheit / Netzwerkarchitekten haben die ersten beiden Ansätze abgelehnt. Der zweite Ansatz ist offensichtlich ein Sicherheitsloch; wenn der Remote-Computer gefährdet ist, ist der lokale Computer jetzt in Gefahr. Der erste Ansatz ist nicht zufriedenstellend, da das neu bereitgestelltes Laufwerk während Dateizugriff durch das Programm eine gemeinsame Ressource zur Verfügung zu anderen Programmen auf dem lokalen Computer ist. Obwohl es durchaus möglich ist, diese vorübergehend zu machen, es ist immer noch ein Loch in ihrer Meinung.

Sie sind offen für die dritte Option, aber die Remote-Netzwerk-Administratoren bestehen auf SFTP statt FTPS und FtpWebRequest unterstützt nur FTPS. SFTP ist die weitere Firewall freundliche Option und es gibt ein paar Bibliotheken, die ich für diesen Ansatz verwenden könnte, aber ich würde es vorziehen, meine Abhängigkeiten zu verringern, wenn ich kann.

Ich habe MSDN für entweder durchsucht ein verwalteter oder ein win32 mittels Remote-File-Sharing, aber ich habe mit etwas Sinnvolles kommen schlug fehl.

Und so frage ich: Gibt es einen anderen Weg? Habe ich eine Super-Geheimnis win32-Funktion, die das tut, was ich will? Oder muss ich verfolgen eine Variante von Option 3?

War es hilfreich?

Lösung

Die Art und Weise, Ihr Problem zu lösen, ist eine Win32-API Hier einige Beispiel-C # -Code, die WNetUseConnection verwendet.

Hinweis für die NETRESOURCE, sollten Sie null für die lpLocalName und lpProvider passieren. Die dwType sollte RESOURCETYPE_DISK sein. Die LpRemoteName sollte \\ Computer.

Andere Tipps

Für Menschen, für eine schnelle Lösung suchen, können Sie die NetworkShareAccesser ich vor kurzem (basierend diese Antwort auf schrieb verwenden (Danke so viel)):

Verwendung:

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

ACHTUNG: Achten Sie unbedingt darauf, dass Dispose des NetworkShareAccesser genannt wird (! Auch wenn Sie Abstürze App), sonst eine offene Verbindung wird unter Windows bleiben. Sie können alle offenen Verbindungen sehen, indem Sie die cmd Aufforderung öffnen und geben Sie net use.

Der 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: 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);
    }
}

AFAIK, Sie brauchen sich nicht zu Karte der UNC-Pfad zu einem Laufwerksbuchstaben, um Anmeldeinformationen für einen Server herzustellen. Ich Batch-Skripten regelmäßig verwendet wie:

net use \\myserver /user:username password

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

net use /delete \\my.server.com

kann jedoch jedes Programm auf das gleiche Konto wie Ihr Programm laufen würde noch in der Lage sein, alles zuzugreifen, hat Zugriff auf username:password. Eine mögliche Lösung könnte darin bestehen, das Programm in seinem eigenen lokalen Benutzerkonto (der Zugriff auf das Konto lokalen UNC, die NET USE genannt) zu isolieren.

Hinweis: Domains SMB accross Verwendung ist nicht ganz gut Nutzung der Technologie, IMO. Wenn die Sicherheit so wichtig ist, ist die Tatsache, dass SMB-Verschlüsselung fehlt, ist ein bisschen wie ein Dämpfer ganz von selbst.

Anstatt WNetUseConnection, würde ich empfehlen NetUseAdd . WNetUseConnection ist ein Vermächtnis-Funktion, die durch WNetUseConnection2 und WNetUseConnection3 abgelöst worden ist, aber alle diese Funktionen ein Netzwerkgerät erstellen, die in Windows Explorer sichtbar ist. NetUseAdd ist das Äquivalent Aufruf net use in einer DOS-Eingabeaufforderung auf einem entfernten Computer zu authentifizieren.

Wenn Sie NetUseAdd dann weitere Versuche nennen gelingt das Verzeichnis sollte zugreifen.

Während ich mich nicht kennen, würde ich sicherlich hoffen, dass # 2 falsch ist ... Ich würde gerne glauben, dass Windows wird sich nicht automatisch die Anmeldungsinformationen geben (am allerwenigsten mein Passwort vergessen!) zu jeder Maschine, geschweige denn eine, die nicht Teil meines Vertrauens.

Unabhängig davon, haben Sie die Personifizierung Architektur erforscht? Ihr Code wird ähnlich wie folgt aussehen:

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

    context.Undo();
}

In diesem Fall wird die token Variable ist ein IntPtr. Um einen Wert für diese Variable zu erhalten, müssen Sie die nicht verwaltete Logonuser Windows-API-Funktion aufrufen müssen. Eine schnelle Reise nach pinvoke.net gibt uns die folgende Signatur:

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

Benutzername, Domäne und Passwort sollte ziemlich offensichtlich zu sein scheinen. Werfen Sie einen Blick auf die verschiedenen Werte, die dwLogonType und dwLogonProvider übergeben werden können, um die man zu bestimmen, die am besten Ihren Bedürfnissen.

Dieser Code wurde nicht getestet, da ich hier nicht eine zweite Domäne haben, wo ich überprüfen kann, aber das sollte man hoffentlich auf dem richtigen Weg.

Die meisten SFTP-Server SCP unterstützen auch die viel einfacher sein können für Bibliotheken zu finden. Sie könnten auch nur einen vorhandenen Client aus dem Code aufrufen wie pscp enthalten mit PuTTY .

Wenn die Art der Datei mit dem Sie arbeiten etwas Einfaches wie eine Text- oder XML-Datei ist, könnte man sogar so weit gehen, wie Sie Ihre eigene Client / Server-Implementierung zu schreiben, die Datei mit so etwas wie .NET Remoting oder Web zu manipulieren Dienstleistungen.

Ich habe 3 gesehen Option implementiert mit JSCAPE Werkzeuge in einer ziemlich einfach Art und Weise . Sie könnten es versuchen. Es ist nicht kostenlos, aber es macht seinen Job.

Hier ist ein minimales POC Klasse w / alle cruft entfernt

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

Sie können direkt \\server\share\folder w / WNetUseConnection verwenden, keine Notwendigkeit strippen es Teil \\server nur vorher.

im befestigen meines vb.net Code basierend auf brian Referenz

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

wie es zu benutzen

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

    If IsNothing(login) Then



        'do your thing on the shared folder



       PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")

    End If

sah ich auf MS die Antworten zu finden. Die erste Lösung übernimmt das Benutzerkonto läuft das Bewerbungsverfahren Zugriff auf den freigegebenen Ordner oder das Laufwerk hat (gleiche Domain). Stellen Sie sicher, dass Ihre DNS aufgelöst wird, oder versuchen, IP-Adresse. Einfach wie folgt vor:

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

Wenn Sie zwischen verschiedenen Domänen .NET 2.0 mit Credentials diesem Modell folgen:

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();
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top