web-dev-qa-db-fra.com

Accès à un fichier partagé (UNC) à partir d'un domaine distant non approuvé avec des informations d'identification

Nous avons rencontré une situation intéressante qui doit être résolue et mes recherches se sont avérées vaines. Je lance donc un appel à la communauté SO pour obtenir de l'aide.

Le problème est le suivant: nous avons besoin d'accéder par programme à un fichier partagé qui ne se trouve pas dans notre domaine et qui ne fait pas partie d'un domaine externe approuvé via le partage de fichiers à distance/UNC. Naturellement, nous devons fournir des informations d'identification à la machine distante.

Généralement, on résout ce problème de deux manières: 

  1. Mappez le partage de fichiers en tant que lecteur et fournissez les informations d'identification à ce moment-là. Cela se fait généralement à l'aide de la commande Net Use ou des fonctions Win32 qui dupliquent Net Use
  2. Accédez au fichier avec un chemin UNC comme si l’ordinateur distant se trouvait sur le domaine et assurez-vous que le compte sous lequel le programme est exécuté est dupliqué (y compris le mot de passe) sur la machine distante en tant qu’utilisateur local. Exploitez essentiellement le fait que Windows fournira automatiquement les informations d'identification de l'utilisateur actuel lorsque celui-ci tente d'accéder à un fichier partagé. 
  3. N'utilisez pas le partage de fichiers à distance. Utilisez FTP (ou un autre moyen) pour transférer le fichier, travaillez-le localement, puis transférez-le. 

Pour des raisons diverses et variées, nos architectes de la sécurité et des réseaux ont rejeté les deux premières approches. La deuxième approche est évidemment une faille de sécurité; si l'ordinateur distant est compromis, l'ordinateur local est maintenant exposé. La première approche n'est pas satisfaisante car le lecteur nouvellement monté est une ressource partagée disponible pour d'autres programmes sur l'ordinateur local lors de l'accès au fichier par le programme. Même s'il est tout à fait possible de faire ce travail temporaire, cela reste un trou dans leur opinion.

Ils sont ouverts à la troisième option, mais les administrateurs réseau distants insistent sur le protocole SFTP plutôt que sur FTPS, et FtpWebRequest ne prend en charge que FTPS. SFTP est l'option la plus compatible avec le pare-feu et il y a quelques bibliothèques que je pourrais utiliser pour cette approche, mais je préférerais réduire mes dépendances si je le peux. 

J'ai cherché dans MSDN un moyen géré ou un moyen win32 d'utiliser le partage de fichiers à distance, mais je n'ai rien trouvé d'utile. 

Et alors je demande: y a-t-il un autre moyen? Ai-je oublié une fonction Win32 super-secrète qui fait ce que je veux? Ou dois-je poursuivre une variante de l'option 3?

138
Randolpho

La solution à votre problème consiste à utiliser une API Win32 appelée WNetUseConnection .
Utilisez cette fonction pour vous connecter à un chemin UNC avec authentification, PAS pour mapper un lecteur.

Cela vous permettra de vous connecter à une machine distante, même si celle-ci ne se trouve pas dans le même domaine, et même si le nom d'utilisateur et le mot de passe sont différents. 

Une fois que vous avez utilisé WNetUseConnection, vous pourrez accéder au fichier via un chemin UNC comme si vous étiez sur le même domaine. Le meilleur moyen est probablement à travers les partages administratifs intégrés.
Exemple: \\ nom_ordinateur\c $\programme fichiers\dossier\fichier.txt

Voici un exemple de code C # qui utilise WNetUseConnection .

Remarque: pour NetResource, vous devez transmettre la valeur null pour lpLocalName et lpProvider. Le type de fichier doit être RESOURCETYPE_DISK. Le lpRemoteName doit être \\ ComputerName.

160
Brian R. Bondy

Pour ceux qui recherchent une solution rapide, vous pouvez utiliser la NetworkShareAccesser que j'ai écrite récemment (basée sur cette réponse (merci beaucoup!)):

Utilisation:

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

ATTENTION: Assurez-vous absolument que Dispose de NetworkShareAccesser est appelé (même si votre application se bloque!), Sinon une connexion ouverte restera sous Windows. Vous pouvez voir toutes les connexions ouvertes en ouvrant l'invite cmd et en entrant Net Use.

Le 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);
    }
}
107
GameScripting

Autant que je sache, vous n'avez pas besoin de mapper le chemin UNC vers une lettre de lecteur pour établir les informations d'identification d'un serveur. J'ai régulièrement utilisé des scripts batch comme:

Net Use \\myserver /user:username password

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

Net Use /delete \\my.server.com

Toutefois, tout programme exécuté sur le même compte que votre programme pourra toujours accéder à tout ce à quoi username:password a accès. Une solution possible pourrait être d'isoler votre programme dans son propre compte d'utilisateur local (l'accès UNC est local pour le compte appelé Net Use).

Remarque: / Utiliser SMB sur plusieurs domaines n’est pas un très bon usage de la technologie, OMI. Si la sécurité est aussi importante que cela, le fait que SMB ne soit pas chiffré est un problème en soi.

15
Jacob

Bien que je ne me connaisse pas, j'espère certainement que le n ° 2 est incorrect ... J'aimerais penser que Windows ne va pas automatiquement donner mes informations de connexion (le moindre de mon mot de passe!) À une machine , encore moins celle qui ne fait pas partie de ma confiance.

Quoi qu'il en soit, avez-vous exploré l'architecture de l'emprunt d'identité? Votre code va ressembler à ceci:

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

    context.Undo();
}

Dans ce cas, la variable token est un IntPtr. Pour obtenir une valeur pour cette variable, vous devez appeler la fonction API Windows non gérée de LogonUser. Un petit tour sur pinvoke.net nous donne la signature suivante:

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

Le nom d'utilisateur, le domaine et le mot de passe devraient sembler assez évidents. Consultez les différentes valeurs pouvant être transmises à dwLogonType et dwLogonProvider afin de déterminer celle qui répond le mieux à vos besoins.

Ce code n'a pas été testé, car je n'ai pas de deuxième domaine ici que je puisse vérifier, mais cela devrait, espérons-le, vous mettre sur la bonne voie.

4
Adam Robinson

Plutôt que WNetUseConnection, je recommanderais NetUseAdd . WNetUseConnection est une fonction héritée remplacée par WNetUseConnection2 et WNetUseConnection3, mais toutes ces fonctions créent un périphérique réseau visible dans l'explorateur Windows. NetUseAdd est l'équivalent d'appeler Net Use dans une invite DOS pour s'authentifier sur un ordinateur distant.

Si vous appelez NetUseAdd, les tentatives suivantes pour accéder au répertoire doivent réussir. 

3
Adam Robinson

La plupart des serveurs SFTP prennent également en charge SCP, ce qui peut être beaucoup plus facile à trouver pour les bibliothèques. Vous pouvez même simplement appeler un client existant à partir de votre code tel que pscp inclus avec PuTTY .

Si le type de fichier que vous utilisez est simple, par exemple un fichier texte ou XML, vous pouvez même aller jusqu'à écrire votre propre implémentation client/serveur pour manipuler le fichier à l'aide de quelque chose comme .NET Remoting ou des services Web.

2
Ryan Bolger

J'ai déjà vu l'option 3 implémentée avec outils JScape de manière assez simple. Vous pourriez essayer. Ce n'est pas gratuit, mais ça fait son travail.

1
DreamSonic

Ici, une classe de POC minimale avec toute la croupe enlevée

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

Vous pouvez directement utiliser \\server\share\folder avec/WNetUseConnection, il n'est pas nécessaire de le rayer au préalable pour \\server partie.

0
wqw

j'attache mon code vb.net basé sur brian reference

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

comment l'utiliser

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
roy.d