Acceso a un archivo compartido (UNC) desde un dominio remoto, no confiable con credenciales

151

Nos hemos encontrado con una situación interesante que debe resolverse, y mis búsquedas han resultado nulas. Por lo tanto, pido ayuda a la comunidad SO.

El problema es este: tenemos la necesidad de acceder mediante programación a un archivo compartido que no está en nuestro dominio, y no está dentro de un dominio externo confiable a través del uso compartido remoto de archivos / UNC. Naturalmente, necesitamos suministrar credenciales a la máquina remota.

Por lo general, uno resuelve este problema de una de dos maneras:

  1. Asigne el recurso compartido de archivos como una unidad y proporcione las credenciales en ese momento. Esto normalmente se hace usando el NET USEcomando o las funciones de Win32 que se duplican NET USE.
  2. Acceda al archivo con una ruta UNC como si la computadora remota estuviera en el dominio y asegúrese de que la cuenta con la que se ejecuta el programa esté duplicada (incluida la contraseña) en la máquina remota como usuario local. Básicamente aprovecha el hecho de que Windows proporcionará automáticamente las credenciales del usuario actual cuando el usuario intente acceder a un archivo compartido.
  3. No use el uso compartido de archivos a distancia. Use FTP (o algún otro medio) para transferir el archivo, trabaje en él localmente y luego vuélvalo a transferir.

Por diversas y diversas razones, nuestros arquitectos de seguridad / redes han rechazado los dos primeros enfoques. El segundo enfoque es obviamente un agujero de seguridad; Si la computadora remota se ve comprometida, la computadora local está ahora en riesgo. El primer enfoque es insatisfactorio porque la unidad recién montada es un recurso compartido disponible para otros programas en la computadora local durante el acceso al archivo por parte del programa. Aunque es bastante posible hacer que esto sea temporal, sigue siendo un agujero en su opinión.

Están abiertos a la tercera opción, pero los administradores de la red remota insisten en SFTP en lugar de FTPS, y FtpWebRequest solo admite FTPS. SFTP es la opción más amigable con el firewall y hay un par de bibliotecas que podría usar para ese enfoque, pero preferiría reducir mis dependencias si pudiera.

He buscado en MSDN un medio administrado o win32 para usar el intercambio remoto de archivos, pero no he podido encontrar nada útil.

Y entonces pregunto: ¿Hay otra manera? ¿Me perdí una función win32 súper secreta que hace lo que quiero? ¿O debo buscar alguna variante de la opción 3?

Randolpho
fuente
Lo he resuelto con el enfoque de suplantación, pero eso es entre 2 máquinas fuera de un dominio. No sé si tendría un problema hablar de un dominio a una computadora fuera del dominio. stackoverflow.com/questions/17221476/…
Wolf5

Respuestas:

174

La forma de resolver su problema es usar una API Win32 llamada WNetUseConnection .
Use esta función para conectarse a una ruta UNC con autenticación, NO para asignar una unidad .

Esto le permitirá conectarse a una máquina remota, incluso si no está en el mismo dominio, e incluso si tiene un nombre de usuario y contraseña diferentes.

Una vez que haya utilizado WNetUseConnection, podrá acceder al archivo a través de una ruta UNC como si estuviera en el mismo dominio. La mejor manera es probablemente a través de las acciones administrativas integradas.
Ejemplo: \\ computername \ c $ \ program files \ Folder \ file.txt

Aquí hay un código C # de muestra que usa WNetUseConnection.
Tenga en cuenta que, para NetResource, debe pasar nulo para lpLocalName y lpProvider. El dwType debe ser RESOURCETYPE_DISK. El lpRemoteName debe ser \\ ComputerName.

using System;
using System.Runtime.InteropServices ;
using System.Threading;

namespace ExtremeMirror
{
    public class PinvokeWindowsNetworking
    {
        #region Consts
        const int RESOURCE_CONNECTED = 0x00000001;
        const int RESOURCE_GLOBALNET = 0x00000002;
        const int RESOURCE_REMEMBERED = 0x00000003;

        const int RESOURCETYPE_ANY = 0x00000000;
        const int RESOURCETYPE_DISK = 0x00000001;
        const int RESOURCETYPE_PRINT = 0x00000002;

        const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
        const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
        const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
        const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
        const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
        const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;

        const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
        const int RESOURCEUSAGE_CONTAINER = 0x00000002;


        const int CONNECT_INTERACTIVE = 0x00000008;
        const int CONNECT_PROMPT = 0x00000010;
        const int CONNECT_REDIRECT = 0x00000080;
        const int CONNECT_UPDATE_PROFILE = 0x00000001;
        const int CONNECT_COMMANDLINE = 0x00000800;
        const int CONNECT_CMD_SAVECRED = 0x00001000;

        const int CONNECT_LOCALDRIVE = 0x00000100;
        #endregion

        #region Errors
        const int NO_ERROR = 0;

        const int ERROR_ACCESS_DENIED = 5;
        const int ERROR_ALREADY_ASSIGNED = 85;
        const int ERROR_BAD_DEVICE = 1200;
        const int ERROR_BAD_NET_NAME = 67;
        const int ERROR_BAD_PROVIDER = 1204;
        const int ERROR_CANCELLED = 1223;
        const int ERROR_EXTENDED_ERROR = 1208;
        const int ERROR_INVALID_ADDRESS = 487;
        const int ERROR_INVALID_PARAMETER = 87;
        const int ERROR_INVALID_PASSWORD = 1216;
        const int ERROR_MORE_DATA = 234;
        const int ERROR_NO_MORE_ITEMS = 259;
        const int ERROR_NO_NET_OR_BAD_PATH = 1203;
        const int ERROR_NO_NETWORK = 1222;

        const int ERROR_BAD_PROFILE = 1206;
        const int ERROR_CANNOT_OPEN_PROFILE = 1205;
        const int ERROR_DEVICE_IN_USE = 2404;
        const int ERROR_NOT_CONNECTED = 2250;
        const int ERROR_OPEN_FILES  = 2401;

        private struct ErrorClass 
        {
            public int num;
            public string message;
            public ErrorClass(int num, string message) 
            {
                this.num = num;
                this.message = message;
            }
        }


        // Created with excel formula:
        // ="new ErrorClass("&A1&", """&PROPER(SUBSTITUTE(MID(A1,7,LEN(A1)-6), "_", " "))&"""), "
        private static ErrorClass[] ERROR_LIST = new ErrorClass[] {
            new ErrorClass(ERROR_ACCESS_DENIED, "Error: Access Denied"), 
            new ErrorClass(ERROR_ALREADY_ASSIGNED, "Error: Already Assigned"), 
            new ErrorClass(ERROR_BAD_DEVICE, "Error: Bad Device"), 
            new ErrorClass(ERROR_BAD_NET_NAME, "Error: Bad Net Name"), 
            new ErrorClass(ERROR_BAD_PROVIDER, "Error: Bad Provider"), 
            new ErrorClass(ERROR_CANCELLED, "Error: Cancelled"), 
            new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"), 
            new ErrorClass(ERROR_INVALID_ADDRESS, "Error: Invalid Address"), 
            new ErrorClass(ERROR_INVALID_PARAMETER, "Error: Invalid Parameter"), 
            new ErrorClass(ERROR_INVALID_PASSWORD, "Error: Invalid Password"), 
            new ErrorClass(ERROR_MORE_DATA, "Error: More Data"), 
            new ErrorClass(ERROR_NO_MORE_ITEMS, "Error: No More Items"), 
            new ErrorClass(ERROR_NO_NET_OR_BAD_PATH, "Error: No Net Or Bad Path"), 
            new ErrorClass(ERROR_NO_NETWORK, "Error: No Network"), 
            new ErrorClass(ERROR_BAD_PROFILE, "Error: Bad Profile"), 
            new ErrorClass(ERROR_CANNOT_OPEN_PROFILE, "Error: Cannot Open Profile"), 
            new ErrorClass(ERROR_DEVICE_IN_USE, "Error: Device In Use"), 
            new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"), 
            new ErrorClass(ERROR_NOT_CONNECTED, "Error: Not Connected"), 
            new ErrorClass(ERROR_OPEN_FILES, "Error: Open Files"), 
        };

        private static string getErrorForNumber(int errNum) 
        {
            foreach (ErrorClass er in ERROR_LIST) 
            {
                if (er.num == errNum) return er.message;
            }
            return "Error: Unknown, " + errNum;
        }
        #endregion

        [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 = "";
        }


        public static string connectToRemote(string remoteUNC, string username, string password) 
        {
            return connectToRemote(remoteUNC, username, password, false);
        }

        public static string connectToRemote(string remoteUNC, string username, string password, bool promptUser) 
        {
            NETRESOURCE nr = new NETRESOURCE();
            nr.dwType = RESOURCETYPE_DISK;
            nr.lpRemoteName = remoteUNC;
            //          nr.lpLocalName = "F:";

            int ret;
            if (promptUser) 
                ret = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
            else 
                ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);

            if (ret == NO_ERROR) return null;
            return getErrorForNumber(ret);
        }

        public static string disconnectRemote(string remoteUNC) 
        {
            int ret = WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, false);
            if (ret == NO_ERROR) return null;
            return getErrorForNumber(ret);
        }
    }
}
Brian R. Bondy
fuente
¿Hay alguna forma de usar funciones como estas para abrir / cerrar explícitamente las conexiones a una máquina de red utilizando las credenciales actuales, es decir, sin proporcionar el nombre de usuario y la contraseña? Estoy específicamente interesado en cerrar una conexión después de acceder a un recurso compartido de archivos.
flipdoubt
No es para conectarse, a menos que la computadora en sí no tenga un nombre de usuario o contraseña. Para desconectarte seguro que puedes. Incluso puede hacerlo a través de la línea de comando en su lugar.
Brian R. Bondy
1
Hola Brian. Los documentos que vincula para decir que puede pasar NULL para el nombre de usuario y contraseña para usar las credenciales actuales. Haré algunas pruebas para ver si esto funciona.
flipdoubt
Pasar nulo para el nombre de usuario / contraseña me permite conectarme, pero ¿cómo puedo probar que me he desconectado? ¿Hay algo en el servidor que pueda ver? En Server 2003, puedo ver las sesiones, pero la lista de sesiones actuales se actualiza tan rápido cuando mi aplicación no usa estas API.
flipdoubt
¿Deben las conexiones abiertas con WNetUseConnectioncerrarse manualmente llamando WNetCancelConnection2? ¿O hay un tiempo de espera inactivo (o algún otro mecanismo) y no tenemos que molestarnos?
w128
123

Para las personas que buscan una solución rápida, pueden usar la NetworkShareAccesserque escribí recientemente (basada en esta respuesta (¡muchas gracias!)):

Uso:

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

ADVERTENCIA: Por favor, haga absolutamente seguro, que Disposede la NetworkShareAccesserque se llama (incluso si APP choques!), De lo contrario una conexión abierta permanecerá en Windows. Puede ver todas las conexiones abiertas abriendo el cmdindicador e ingresando net use.

El código:

/// <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);
    }
}
GameScripting
fuente
2
necesita también using System.Runtime.InteropServices;y using System.ComponentModel;para DllImportyWin32Exception
Kᴀτᴢ
Esta solución ha detenido mi búsqueda de día largo. ¡¡¡Gracias!!! Funciona bastante bien según sea necesario.
Venkat el
1
Estoy tratando de usar su solución con una cuenta de usuario local en la máquina remota, pero sigo recibiendo un error de acceso denegado. ¿Su solución solo funcionará para cuentas de red?
M3NTA7
1
La cuenta existe en la máquina remota, pero no es una cuenta de red. Es una cuenta de máquina local. He intentado configurar el dominio con el nombre de la máquina. También he otorgado permisos completos a la cuenta de usuario local en la carpeta compartida, pero se me niega el acceso. ¿Alguna idea de por qué esto puede estar sucediendo? Gracias.
M3NTA7
2
Nota: eliminar el objeto no parece borrar las credenciales del sistema (Windows 10); Puedo acceder a los archivos en la computadora remota después de que la conexión ha sido "cancelada". Volver a iniciar sesión en mi cuenta de usuario o reiniciar mi computadora parece borrar esta caché interna.
Tim Cooper
16

AFAIK, no necesita asignar la ruta UNC a una letra de unidad para establecer credenciales para un servidor. Usé regularmente scripts por lotes como:

net use \\myserver /user:username password

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

net use /delete \\my.server.com

Sin embargo, cualquier programa que se ejecute en la misma cuenta que su programa aún podría acceder a todo lo que username:passwordtiene acceso. Una posible solución podría ser aislar su programa en su propia cuenta de usuario local (el acceso UNC es local a la cuenta que llamó NET USE).

Nota: El uso de SMB entre dominios no es un buen uso de la tecnología, IMO. Si la seguridad es tan importante, el hecho de que SMB carece de cifrado es un poco un obstáculo por sí solo.

Jacob
fuente
Si tiene razón acerca de que el acceso UNC solo está disponible para la cuenta que llamó NET USE, ese podría ser un enfoque viable. Sin embargo, ¿está seguro de que necesitamos usar una cuenta local? ¿No sería la NET USEllamada local a la máquina en la que se llamó? Me has dado un buen camino de investigación
Randolpho
AFAIK, y puedo estar equivocado, el acceso UNC solo estará disponible para el principal de seguridad específico (cuenta SAM, lo que sea) bajo el cual se realizó la llamada al USO NETO. Puede verificar esto utilizando RunAs para mapear la ruta y luego intentando acceder desde otra cuenta.
Jacob
en mi caso, tuve que usar net use \\ myserver / user: username @ domain password ya que el usuario está en un dominio diferente.
StarCub
4

En lugar de WNetUseConnection, recomendaría NetUseAdd . WNetUseConnection es una función heredada que ha sido reemplazada por WNetUseConnection2 y WNetUseConnection3, pero todas esas funciones crean un dispositivo de red que es visible en el Explorador de Windows. NetUseAdd es el equivalente a llamar al uso de la red en un indicador de DOS para autenticarse en una computadora remota.

Si llama a NetUseAdd, los intentos posteriores de acceder al directorio deberían tener éxito.

Adam Robinson
fuente
1
@ Adam Robinson: Esto no es cierto. No existe tal WNetUseConnection2 ni WNetUseConnection3. Creo que estás pensando en WNetAddConnection siendo reemplazado por WNetAddConnection2 y WnetAddConnection3. Además, la información que proporcionó al respecto no es cierta.
Brian R. Bondy el
WNetUseConnection es como WNetAddConnection3, pero también tiene una capacidad opcional para crear una unidad local asignada. Que no tienes que usar.
Brian R. Bondy el
@ BrianR.Bondy De hecho, existen, pero no se implementan como C #. Fuente: docs.microsoft.com/da-dk/windows/win32/api/lmuse/… Cita: "También puede usar las funciones WNetAddConnection2 y WNetAddConnection3 para redirigir un dispositivo local a un recurso de red".
Thomas Williams
4

Si bien no me conozco, ciertamente espero que el número 2 sea incorrecto ... Me gustaría pensar que Windows no va a dar AUTOMÁTICAMENTE mi información de inicio de sesión (¡menos que nada mi contraseña!) A ninguna máquina , mucho menos uno que no es parte de mi confianza.

De todos modos, ¿ha explorado la arquitectura de suplantación? Su código se verá similar a esto:

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

    context.Undo();
}

En este caso, la tokenvariable es un IntPtr. Para obtener un valor para esta variable, deberá llamar a la función API de Windows LogonUser no administrada. Un viaje rápido a pinvoke.net nos da la siguiente firma:

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

El nombre de usuario, el dominio y la contraseña deberían parecer bastante obvios. Eche un vistazo a los diversos valores que se pueden pasar a dwLogonType y dwLogonProvider para determinar cuál se adapta mejor a sus necesidades.

Este código no se ha probado, ya que no tengo un segundo dominio aquí donde pueda verificarlo, pero espero que esto lo ponga en el camino correcto.

Adam Robinson
fuente
77
La suplantación no funcionará cuando intente utilizar una identificación de inicio de sesión de un dominio no confiable. El ID de usuario debe poder iniciar sesión localmente.
Moose
Sí, probamos esta ruta, terminó siendo como dice @Moose: el dominio no es de confianza y, por lo tanto, la suplantación no funcionará.
Randolpho
Sí, una vez que vi ese comentario, es por eso que publiqué la respuesta usando NetUseAdd (la principal diferencia entre este y las funciones WNetUseConnection y WNetAddConnection es que NetUseAdd no hace que la conexión sea visible en el Explorador de Windows).
Adam Robinson el
La suplantación tampoco funciona en el mismo dominio, en mis pruebas me sigue respondiendo con Acceso denegado tratando de leer un archivo en una carpeta compartida con una cuenta de administrador (administrador en ambas máquinas). Entonces, creo que este no es el enfoque correcto.
lidermin
4

Aquí una clase mínima de POC con todo el cruft eliminado

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

Puede usar directamente \\server\share\folderw / WNetUseConnection, no es necesario despojarlo a la \\serverparte solo de antemano.

wqw
fuente
2

La mayoría de los servidores SFTP también admiten SCP, lo que puede ser mucho más fácil para encontrar bibliotecas. Incluso podría llamar a un cliente existente desde su código como pscp incluido con PuTTY .

Si el tipo de archivo con el que está trabajando es algo simple, como un archivo de texto o XML, incluso podría llegar a escribir su propia implementación de cliente / servidor para manipular el archivo usando algo como .NET Remoting o servicios web.

Ryan Bolger
fuente
1

He visto la opción 3 implementada con herramientas JScape de una manera bastante sencilla. Puedes intentarlo. No es gratis, pero hace su trabajo.

DreamSonic
fuente
1

adjunto mi código vb.net basado en la referencia de brian

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

cómo usarlo

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

    If IsNothing(login) Then



        'do your thing on the shared folder



       PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")

    End If
roy.d
fuente
-1

Miré a MS para encontrar las respuestas. La primera solución supone que la cuenta de usuario que ejecuta el proceso de la aplicación tiene acceso a la carpeta o unidad compartida (mismo dominio). Asegúrese de que su DNS esté resuelto o intente usar la dirección IP. Simplemente haga lo siguiente:

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

Si desea en diferentes dominios .NET 2.0 con credenciales, siga este modelo:

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();
Kentonbmax
fuente
parece interesante
DeerSpotter