220

When connecting to a network share for which the current user (in my case, a network enabled service user) has no rights, name and password have to be provided.

I know how to do this with Win32 functions (the WNet* family from mpr.dll), but would like to do it with .Net (2.0) functionality.

What options are available?

Maybe some more information helps:

  • The use case is a windows service, not an Asp.Net application.
  • The service is running under an account which has no rights on the share.
  • The user account needed for the share is not known on the client side.
  • Client and server are not members of the same domain.
gyrolf
  • 3,772
  • 5
  • 26
  • 22
  • 8
    While I'm not giving you a useful answer, I can supply an anti-answer.. Impersonation and spawning a process as Marc posited will not work when the server and the client are not in the same domain, unless there is a trust between the two domains. If there is a trust then I think it will work. I would have just replied as a comment to Marc's but I don't have enough rep to comment. :-/ – Moose Nov 17 '08 at 14:23
  • Related - http://stackoverflow.com/questions/17786037/copy-files-with-authentication-in-c-sharp – vapcguy Jan 24 '17 at 16:38

12 Answers12

361

I liked Mark Brackett's answer so much that I did my own quick implementation. Here it is if anyone else needs it in a hurry:

public class NetworkConnection : IDisposable
{
    string _networkName;

    public NetworkConnection(string networkName, 
        NetworkCredential credentials)
    {
        _networkName = networkName;

        var netResource = new NetResource()
        {
            Scope = ResourceScope.GlobalNetwork,
            ResourceType = ResourceType.Disk,
            DisplayType = ResourceDisplaytype.Share,
            RemoteName = networkName
        };

        var userName = string.IsNullOrEmpty(credentials.Domain)
            ? credentials.UserName
            : string.Format(@"{0}\{1}", credentials.Domain, credentials.UserName);

        var result = WNetAddConnection2(
            netResource, 
            credentials.Password,
            userName,
            0);
            
        if (result != 0)
        {
            throw new Win32Exception(result);
        }   
    }

    ~NetworkConnection()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        WNetCancelConnection2(_networkName, 0, true);
    }

    [DllImport("mpr.dll")]
    private static extern int WNetAddConnection2(NetResource netResource, 
        string password, string username, int flags);

    [DllImport("mpr.dll")]
    private static extern int WNetCancelConnection2(string name, int flags,
        bool force);
}

[StructLayout(LayoutKind.Sequential)]
public class NetResource
{
    public ResourceScope Scope;
    public ResourceType ResourceType;
    public ResourceDisplaytype DisplayType;
    public int Usage;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string LocalName;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string RemoteName;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string Comment;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string Provider;
}

public enum ResourceScope : int
{
    Connected = 1,
    GlobalNetwork,
    Remembered,
    Recent,
    Context
};

public enum ResourceType : int
{
    Any = 0,
    Disk = 1,
    Print = 2,
    Reserved = 8,
}

public enum ResourceDisplaytype : int
{
    Generic = 0x0,
    Domain = 0x01,
    Server = 0x02,
    Share = 0x03,
    File = 0x04,
    Group = 0x05,
    Network = 0x06,
    Root = 0x07,
    Shareadmin = 0x08,
    Directory = 0x09,
    Tree = 0x0a,
    Ndscontainer = 0x0b
}
Diego Mazzaro
  • 2,734
  • 1
  • 20
  • 21
Luke Quinane
  • 16,447
  • 13
  • 69
  • 88
  • 10
    It really should be `throw new Win32Exception(result);`, since WNetAddConnection2 returns win32 error codes (`ERROR_XXX`) – torvin May 16 '11 at 08:22
  • Note that if you are connecting to a domain resource, it should be `string.Format(@"{0}\{1}", credentials.Domain, credentials.UserName)` instead of just `credentials.UserName`. – AngryHacker Aug 14 '12 at 16:59
  • Can you explain where does `ResourceScope.Recent` and `ResourceScope.Context` come from? Since dwScope seems to have only 3 values possible (as written [here](http://msdn.microsoft.com/en-us/library/windows/desktop/aa385353%28v=vs.85%29.aspx)): `RESOURCE_CONNECTED`, `RESOURCE_GLOBALNET`, `RESOURCE_REMEMBERED`. Also, where does `ResourceType.Reserved` comes from? – Otiel Nov 28 '12 at 15:32
  • I got those structures from pinvoke.net [here](http://www.pinvoke.net/default.aspx/Enums/ResourceScope.html) and [here](http://www.pinvoke.net/default.aspx/Enums/ResourceType.html). If you search around for RESOURCE_RECENT and RESOURCETYPE_RESERVED there are a few reactos hits so perhaps they are undocumented values? – Luke Quinane Nov 28 '12 at 21:56
  • 1
    Great solution, thanks. I wanted to add that in our case, we were connecting from inside our network, to a Windows2008R2 server on the DMZ with an account that was local to the DMZ box. In this case, paths with \\server\d$\somepath in the path would not work, we had to explicity share the directory such that we could visit it using \\server\somepath instead. Otherwise we would get remote connection errors. – Matt Apr 30 '13 at 20:37
  • 2
    This is a brilliant little piece of code. Needed to logon to a UNIX system to get a directory listing for printing to an MVC5 web application and this did the trick. +1!!! – Tay Jan 27 '14 at 09:07
  • Nice class. I just gave a link to this anwer for accessing a remote registry with credentials: http://stackoverflow.com/a/28749093/4547223 – Rainer Schaack Feb 26 '15 at 17:39
  • So solution works perfect. But in fact connection is not clodes after `NetworkConnection` dispose. After object dispose i didnt see connetion via `net use` as expected, but i can still acess folder for a while. So question is : how to prevent acess to shared folder after object disposion. Anyone knows reason why this happens? – MelnikovI Oct 20 '15 at 07:22
  • 3
    The following using statements are required in order for the code above to compile: using System.Net; using System.Runtime.InteropServices; using System.ComponentModel; – Matt Nelson Dec 17 '15 at 17:00
  • 5
    sorry to refresh that old thread, but looks like it doesn't close connection after block is finished. I have a program to upload few pictures, first one goes fine, second one giving fail. Connection is released when program is closed. Any advise? – arti Dec 24 '15 at 08:52
  • 4
    We had the same problem as you, @arti . By just setting the username and password on the `NetworkCredential` object the application was able to connect once to the network drive. After that we got an **ERROR_LOGON_FAILURE** on each attempt until the application was restarted. We then tried to supply the domain on the `NetworkCredential` object as well, and suddenly it worked! I have no idea why this fixed the issue, especially the fact that it worked to connect once without the domain. – lsmeby Aug 18 '16 at 13:04
  • 1
    According to [the documentation of WNetAddConnection2](https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx), the `Scope` and `DisplayType` values are ignored, so there's no need to set them. – Heinzi Sep 01 '17 at 15:16
  • I used this solution many times and it worked perfect. But now I am trying to connect a remote share folder with a user that has no password set and it didn't work. Is this possible? What I need to change to make it work? – nlareu Sep 05 '17 at 14:57
  • You may want to add networkName-parameter validation, or autofix this using something like this: ` _networkName = networkName.TrimEnd(System.IO.Path.DirectorySeparatorChar); ` – Dutchman Aug 29 '18 at 02:51
  • @LukeQuinane i removed message part from Win32Exception that way the exception.Message will become the actual message from the error code. – Jim Wolff Sep 26 '18 at 12:10
  • Two comments: my mpr.dll (Windows 7) does not expose methods WNetAddConnection2 and WNetCancelConnection2, instead I had to use WNetAddConnection2A and WNetCancelConnection2A... Second, the path cannot end with "\"... If I remove it it works, but if I leave everything the same with the \ at the end, it issues an error saying that it cannot faind the path. – Alicia Oct 03 '18 at 06:54
  • 2
    @Alicia "my mpr.dll (Windows 7) does not expose methods WNetAddConnection2 and WNetCancelConnection2, instead I had to use WNetAddConnection2A and WNetCancelConnection2A" That is correct. Window DLLs usually expose both an ANSI (A) or UNICODE (W) version of functions. Depending on the values passed to the `DllImport` attribute in the CharSet argument, P/Invoke will automagically call the right version, in which case the A or W postfix can be omitted. – Tom Lint Nov 01 '19 at 14:02
  • @TomLint yes, actually after posting this comment I opened a question (https://stackoverflow.com/questions/52643817/why-do-these-dlls-have-two-apparently-identical-entry-points) to get more details on that point, and I got them :) – Alicia Nov 03 '19 at 10:59
  • Reading in the `remoteHost` as a `Uri` could really help this solution. I had to keep fighting with the actual path I was passing in and receiving fairly vague answers. That was until I found https://stackoverflow.com/questions/1363679/a-specified-logon-session-does-not-exist-it-may-already-have-been-terminated/1364198#1364198 and helped do some proper path verification. – vandsh Dec 12 '19 at 20:21
  • @LukeQuinane - You should create a NuGet package for this. That would be a great help. – Ankush Jain Apr 22 '20 at 13:51
  • Brilliant code. I had a shared folder on an Azure server and had to copy files locally. This worked beautifully. – Himanshu Patel Sep 02 '20 at 18:43
  • Getting error, The type or namespace name 'NetworkCredential' could not be found (are you missing a using directive or an assembly reference?) can someone please help, from where should I import it ? – Vasanth Jan 20 '21 at 12:17
  • this solution worked for me, but after a while out of nowhere, `WNetAddConnection2` started returning error code 1219. three changes solved the issue for me: 1. ignore error code 1219; 2. provide the domain to the `NetworkCredential` object; 3. create a and then close a new connection (`NetworkConnection`) every time when accessing the remote share (previously multiple file operations were being done part of a single connection doing multiple actions, which was closed/disposed at the end) – SzilardD Feb 23 '21 at 10:57
  • According to [DllImportAttribute.CharSet](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportattribute.charset?view=netframework-4.8): _ The default enumeration member for C# and Visual Basic is CharSet.Ansi_. Also see [Specify a character set](https://learn.microsoft.com/en-us/dotnet/framework/interop/specifying-a-character-set). – Tu deschizi eu inchid May 14 '23 at 22:40
  • According to [Default marshalling for strings](https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshalling-for-strings#strings-used-in-interfaces): _UnmanagedType.LPWStr: A pointer to a null-terminated array of Unicode characters_. Therefore, one needs to use `[DllImport("mpr.dll", CharSet = CharSet.Unicode)]` if specifying `[MarshalAs(UnmanagedType.LPWStr)]`. Otherwise, one receives the following error: _System.ComponentModel.Win32Exception: 'The network name cannot be found'_ – Tu deschizi eu inchid May 14 '23 at 22:44
164

You can either change the thread identity, or P/Invoke WNetAddConnection2. I prefer the latter, as I sometimes need to maintain multiple credentials for different locations. I wrap it into an IDisposable and call WNetCancelConnection2 to remove the creds afterwards (avoiding the multiple usernames error):

using (new NetworkConnection(@"\\server\read", readCredentials))
using (new NetworkConnection(@"\\server2\write", writeCredentials)) {
   File.Copy(@"\\server\read\file", @"\\server2\write\file");
}
Mark Brackett
  • 84,552
  • 17
  • 108
  • 152
  • 4
    The service isn't member of the target domain - impersonation cannot work since you wouldn't be able to create the security token locally and impersonate with it. PInvoke is the *only* way. – stephbu Nov 17 '08 at 17:31
  • @MarkBrackett I know this is an old answer, but maybe you still know... will the access be granted to the program only or also to the logged in user via explorer? – Breeze Mar 21 '16 at 10:06
  • @Breeze - I haven't tested it, but I'd expect it authenticate for the logon session; so if your program is running as the logged on user, they'd have access as well (at least for the duration of the operation). – Mark Brackett Mar 21 '16 at 15:45
  • @MarkBrackett I meanwhile tested it and your expectation was correct. – Breeze Mar 22 '16 at 08:18
  • 17
    The definitions of readCredentials and writeCredentials could be included in the answer. – Anders Lindén May 03 '17 at 09:34
  • 3
    If you're getting *Error 53*, make sure the path isn't ending with a "\" – Mustafa Sadedil Apr 03 '18 at 11:52
62

Today 7 years later I'm facing the same issue and I'd like to share my version of the solution.

It is copy & paste ready :-) Here it is:

Step 1

In your code (whenever you need to do something with permissions)

ImpersonationHelper.Impersonate(domain, userName, userPassword, delegate
                            {
                                //Your code here 
                                //Let's say file copy:
                                if (!File.Exists(to))
                                {
                                    File.Copy(from, to);
                                }
                            });

Step 2

The Helper file which does a magic

using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;    
using Microsoft.Win32.SafeHandles;


namespace BlaBla
{
    public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle()
            : base(true)
        {
        }

        [DllImport("kernel32.dll")]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);

        protected override bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }
    }

    public class ImpersonationHelper
    {
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
        int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private extern static bool CloseHandle(IntPtr handle);

        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        public static void Impersonate(string domainName, string userName, string userPassword, Action actionToExecute)
        {
            SafeTokenHandle safeTokenHandle;
            try
            {

                const int LOGON32_PROVIDER_DEFAULT = 0;
                //This parameter causes LogonUser to create a primary token.
                const int LOGON32_LOGON_INTERACTIVE = 2;

                // Call LogonUser to obtain a handle to an access token.
                bool returnValue = LogonUser(userName, domainName, userPassword,
                    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                    out safeTokenHandle);
                //Facade.Instance.Trace("LogonUser called.");

                if (returnValue == false)
                {
                    int ret = Marshal.GetLastWin32Error();
                    //Facade.Instance.Trace($"LogonUser failed with error code : {ret}");

                    throw new System.ComponentModel.Win32Exception(ret);
                }

                using (safeTokenHandle)
                {
                    //Facade.Instance.Trace($"Value of Windows NT token: {safeTokenHandle}");
                    //Facade.Instance.Trace($"Before impersonation: {WindowsIdentity.GetCurrent().Name}");

                    // Use the token handle returned by LogonUser.
                    using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
                    {
                        using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
                        {
                            //Facade.Instance.Trace($"After impersonation: {WindowsIdentity.GetCurrent().Name}");
                            //Facade.Instance.Trace("Start executing an action");

                            actionToExecute();

                            //Facade.Instance.Trace("Finished executing an action");
                        }
                    }
                    //Facade.Instance.Trace($"After closing the context: {WindowsIdentity.GetCurrent().Name}");
                }

            }
            catch (Exception ex)
            {
                //Facade.Instance.Trace("Oh no! Impersonate method failed.");
                //ex.HandleException();
                //On purpose: we want to notify a caller about the issue /Pavel Kovalev 9/16/2016 2:15:23 PM)/
                throw;
            }
        }
    }
}
Pavel Kovalev
  • 7,521
  • 5
  • 45
  • 67
  • 2
    @MohammadRashid According to the documentation on [LogonUser](https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx), it only works for users on the local computer: "The LogonUser function attempts to log a user on to the local computer. The local computer is the computer from which LogonUser was called. You cannot use LogonUser to log on to a remote computer. " You'll receive an error "Win32Exception: The user name or password is incorrect." So I suppose the machines need to be on the same domain at least. – Charles Chen Oct 09 '17 at 17:19
  • 1
    @CharlesChen Just proved that this works fine across domains, FYI. The server I'm running this on is in a DMZ, and is definitely connecting to a file server on a different domain, through a firewall. Killer snippet Pavel, you are the man, and this should probably be the accepted answer today. – Brian MacKay Oct 26 '17 at 13:09
  • This is A GREAT SOLUTION! Thank you, Pavel Kovalev. – STLDev Sep 27 '19 at 21:39
  • does this work on ldap? it says that i don't have a logon server available. im using ldap auth – Julius Limson Nov 11 '19 at 08:09
  • some more years later, it seems, that this is not working for me. I am using windows 10 on both ends of the connection. the ip-adress of the target pc is 192.168.10.255 and the user is "user", a local user on this pc. I tried domain with and without \\ also user with and without domain, but i cannot login. Login via windows works perfect. – Wolfgang Roth Sep 10 '20 at 12:51
  • @WolfgangRoth The LogonUser function attempts to log a user on to the local computer. The local computer is the computer from which LogonUser was called. You cannot use LogonUser to log on to a remote computer. So you can only use it on your local device to access some (shared) folders. – Pavel Kovalev Sep 10 '20 at 16:05
28

I searched lots of methods and i did it my own way. You have to open a connection between two machine via command prompt NET USE command and after finishing your work clear the connection with command prompt NET USE "myconnection" /delete.

You must use Command Prompt process from code behind like this:

var savePath = @"\\servername\foldername\myfilename.jpg";
var filePath = @"C:\\temp\myfileTosave.jpg";

Usage is simple:

SaveACopyfileToServer(filePath, savePath);

Here is functions:

using System.IO
using System.Diagnostics;


public static void SaveACopyfileToServer(string filePath, string savePath)
    {
        var directory = Path.GetDirectoryName(savePath).Trim();
        var username = "loginusername";
        var password = "loginpassword";
        var filenameToSave = Path.GetFileName(savePath);

        if (!directory.EndsWith("\\"))
            filenameToSave = "\\" + filenameToSave;

        var command = "NET USE " + directory + " /delete";
        ExecuteCommand(command, 5000);

        command = "NET USE " + directory + " /user:" + username + " " + password;
        ExecuteCommand(command, 5000);

        command = " copy \"" + filePath + "\"  \"" + directory + filenameToSave + "\"";

        ExecuteCommand(command, 5000);


        command = "NET USE " + directory + " /delete";
        ExecuteCommand(command, 5000);
    }

And also ExecuteCommand function is:

public static int ExecuteCommand(string command, int timeout)
    {
        var processInfo = new ProcessStartInfo("cmd.exe", "/C " + command)
                              {
                                  CreateNoWindow = true, 
                                  UseShellExecute = false, 
                                  WorkingDirectory = "C:\\",
                              };

        var process = Process.Start(processInfo);
        process.WaitForExit(timeout);
        var exitCode = process.ExitCode;
        process.Close();
        return exitCode;
    } 

This functions worked very fast and stable for me.

Hakan KOSE
  • 751
  • 9
  • 16
14

The Luke Quinane solution looks good, but did work only partially in my ASP.NET MVC application. Having two shares on the same server with different credentials I could use the impersonation only for the first one.

The problem with WNetAddConnection2 is also that it behaves differently on different windows versions. That is why I looked for alternatives and found the LogonUser function. Here is my code which also works in ASP.NET:

public sealed class WrappedImpersonationContext
{
    public enum LogonType : int
    {
        Interactive = 2,
        Network = 3,
        Batch = 4,
        Service = 5,
        Unlock = 7,
        NetworkClearText = 8,
        NewCredentials = 9
    }

    public enum LogonProvider : int
    {
        Default = 0,  // LOGON32_PROVIDER_DEFAULT
        WinNT35 = 1,
        WinNT40 = 2,  // Use the NTLM logon provider.
        WinNT50 = 3   // Use the negotiate logon provider.
    }

    [DllImport("advapi32.dll", EntryPoint = "LogonUserW", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool LogonUser(String lpszUsername, String lpszDomain,
        String lpszPassword, LogonType dwLogonType, LogonProvider dwLogonProvider, ref IntPtr phToken);

    [DllImport("kernel32.dll")]
    public extern static bool CloseHandle(IntPtr handle);

    private string _domain, _password, _username;
    private IntPtr _token;
    private WindowsImpersonationContext _context;

    private bool IsInContext
    {
        get { return _context != null; }
    }

    public WrappedImpersonationContext(string domain, string username, string password)
    {
        _domain = String.IsNullOrEmpty(domain) ? "." : domain;
        _username = username;
        _password = password;
    }

    // Changes the Windows identity of this thread. Make sure to always call Leave() at the end.
    [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
    public void Enter()
    {
        if (IsInContext)
            return;

        _token = IntPtr.Zero;
        bool logonSuccessfull = LogonUser(_username, _domain, _password, LogonType.NewCredentials, LogonProvider.WinNT50, ref _token);
        if (!logonSuccessfull)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        WindowsIdentity identity = new WindowsIdentity(_token);
        _context = identity.Impersonate();

        Debug.WriteLine(WindowsIdentity.GetCurrent().Name);
    }

    [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
    public void Leave()
    {
        if (!IsInContext)
            return;

        _context.Undo();

        if (_token != IntPtr.Zero)
        {
            CloseHandle(_token);
        }
        _context = null;
    }
}

Usage:

var impersonationContext = new WrappedImpersonationContext(Domain, Username, Password);
impersonationContext.Enter();

//do your stuff here

impersonationContext.Leave();
VladL
  • 12,769
  • 10
  • 63
  • 83
  • 2
    this approach worked well for me, but noticed in my testing that when using a bad password with a domain user account, that user is immediately throw into locked status. our domain policy calls for a 3 failed login attempts before that happens, but via this approach one bad attempt and you're locked. So, use with caution... – kellyb Oct 02 '15 at 17:46
6

For VB.lovers the VB.NET equivalent of Luke Quinane's code (thanks Luke!)

Imports System
Imports System.Net
Imports System.Runtime.InteropServices
Imports System.ComponentModel

Public Class NetworkConnection
    Implements IDisposable

    Private _networkName As String

    Public Sub New(networkName As String, credentials As NetworkCredential)
        _networkName = networkName

        Dim netResource = New NetResource() With {
             .Scope = ResourceScope.GlobalNetwork,
             .ResourceType = ResourceType.Disk,
             .DisplayType = ResourceDisplaytype.Share,
             .RemoteName = networkName
        }

        Dim userName = If(String.IsNullOrEmpty(credentials.Domain), credentials.UserName, String.Format("{0}\{1}", credentials.Domain, credentials.UserName))

        Dim result = WNetAddConnection2(NetResource, credentials.Password, userName, 0)

        If result <> 0 Then
            Throw New Win32Exception(result, "Error connecting to remote share")
        End If
    End Sub

    Protected Overrides Sub Finalize()
        Try
            Dispose (False)
        Finally
            MyBase.Finalize()
        End Try
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose (True)
        GC.SuppressFinalize (Me)
    End Sub

    Protected Overridable Sub Dispose(disposing As Boolean)
        WNetCancelConnection2(_networkName, 0, True)
    End Sub

    <DllImport("mpr.dll")> _
    Private Shared Function WNetAddConnection2(netResource As NetResource, password As String, username As String, flags As Integer) As Integer
    End Function

    <DllImport("mpr.dll")> _
    Private Shared Function WNetCancelConnection2(name As String, flags As Integer, force As Boolean) As Integer
    End Function

End Class

<StructLayout(LayoutKind.Sequential)> _
Public Class NetResource
    Public Scope As ResourceScope
    Public ResourceType As ResourceType
    Public DisplayType As ResourceDisplaytype
    Public Usage As Integer
    Public LocalName As String
    Public RemoteName As String
    Public Comment As String
    Public Provider As String
End Class

Public Enum ResourceScope As Integer
    Connected = 1
    GlobalNetwork
    Remembered
    Recent
    Context
End Enum

Public Enum ResourceType As Integer
    Any = 0
    Disk = 1
    Print = 2
    Reserved = 8
End Enum

Public Enum ResourceDisplaytype As Integer
    Generic = &H0
    Domain = &H1
    Server = &H2
    Share = &H3
    File = &H4
    Group = &H5
    Network = &H6
    Root = &H7
    Shareadmin = &H8
    Directory = &H9
    Tree = &HA
    Ndscontainer = &HB
End Enum
3

OK... I can resond..

Disclaimer: I just had an 18+ hour day (again).. I'm old and forgetfull.. I can't spell.. I have a short attention span so I better respond fast.. :-)

Question:

Is it possible to change the thread principal to an user with no account on the local machine?

Answer:

Yes, you can change a thread principal even if the credentials you are using are not defined locally or are outside the "forest".

I just ran into this problem when trying to connect to an SQL server with NTLM authentication from a service. This call uses the credentials associated with the process meaning that you need either a local account or a domain account to authenticate before you can impersonate. Blah, blah...

But...

Calling LogonUser(..) with the attribute of ????_NEW_CREDENTIALS will return a security token without trying to authenticate the credentials. Kewl.. Don't have to define the account within the "forest". Once you have the token you might have to call DuplicateToken() with the option to enable impersonation resulting in a new token. Now call SetThreadToken( NULL, token ); (It might be &token?).. A call to ImpersonateLoggedonUser( token ); might be required, but I don't think so. Look it up..

Do what you need to do..

Call RevertToSelf() if you called ImpersonateLoggedonUser() then SetThreadToken( NULL, NULL ); (I think... look it up), and then CloseHandle() on the created handles..

No promises but this worked for me... This is off the top of my head (like my hair) and I can't spell!!!

3

One option that might work is using WindowsIdentity.Impersonate (and change the thread principal) to become the desired user, like so. Back to p/invoke, though, I'm afraid...

Another cheeky (and equally far from ideal) option might be to spawn a process to do the work... ProcessStartInfo accepts a .UserName, .Password and .Domain.

Finally - perhaps run the service in a dedicated account that has access? (removed as you have clarified that this isn't an option).

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    i don't think the process thing is such a bad idea. google put out some whitepapers about the benefits of multiprocessing in chrome. – Dustin Getz Nov 17 '08 at 13:26
  • Is it possible to change the thread principal to an user with no account on the local machine? – gyrolf Nov 17 '08 at 13:52
  • To be honest, I simply don't know... You'd have to try LogonUser with a different domain to find out. – Marc Gravell Nov 17 '08 at 14:00
3

This must be the stupidest way, but it's been useful to me lately and it's ridiculously simple.
Only works on Windows, of course.

Process.Start("CMDKEY", @"/add:""NetworkName"" /user:""Username"" /pass:""Password""");

Then you'll probably want to use WaitForExit() before trying to access the share.

You may remove the credentials at the end, using the CMDKEY command once more.

matpop
  • 1,969
  • 1
  • 19
  • 36
2

You should be looking at adding a line like this:

<identity impersonate="true" userName="domain\user" password="****" />

Into your web.config.

More Information.

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
  • Some Corporate Security prevents the use of impersonate because they are unable to track the application using it and must be in same or trusted domain. I think impersonate support is spotted. A domain service account with pinvoke appears to be the way to go. – Jim Feb 19 '19 at 04:13
1

If you can't create an locally valid security token, it seems like you've ruled all out every option bar Win32 API and WNetAddConnection*.

Tons of information on MSDN about WNet - PInvoke information and sample code that connects to a UNC path here:

http://www.pinvoke.net/default.aspx/mpr/WNetAddConnection2.html#

MSDN Reference here:

http://msdn.microsoft.com/en-us/library/aa385391(VS.85).aspx

stephbu
  • 5,072
  • 26
  • 42
1

Also ported to F# to use with FAKE

module NetworkShare

open System
open System.ComponentModel
open System.IO
open System.Net
open System.Runtime.InteropServices

type ResourceScope =
| Connected = 1
| GlobalNetwork = 2
| Remembered = 3
| Recent = 4
type ResourceType =
| Any = 0
| Disk = 1
| Print = 2
| Reserved = 8
type ResourceDisplayType =
| Generic = 0x0
| Domain = 0x01
| Server = 0x02
| Share = 0x03
| File = 0x04
| Group = 0x05
| Network = 0x06
| Root = 0x07
| Shareadmin = 0x08
| Directory = 0x09
| Tree = 0x0a
| Ndscontainer = 0x0b

//Uses of this construct may result in the generation of unverifiable .NET IL code.
#nowarn "9"
[<StructLayout(LayoutKind.Sequential)>]
type NetResource =
  struct
    val mutable Scope : ResourceScope
    val mutable ResourceType : ResourceType
    val mutable DisplayType : ResourceDisplayType
    val mutable Usage : int
    val mutable LocalName : string
    val mutable RemoteName : string
    val mutable Comment : string
    val mutable Provider : string
    new(name) = {
      // lets preset needed fields
      NetResource.Scope = ResourceScope.GlobalNetwork
      ResourceType = ResourceType.Disk
      DisplayType = ResourceDisplayType.Share
      Usage = 0
      LocalName = null
      RemoteName = name
      Comment = null
      Provider = null
    }
  end

type WNetConnection(networkName : string, credential : NetworkCredential) =
  [<Literal>]
  static let Mpr = "mpr.dll"
  [<DllImport(Mpr, EntryPoint = "WNetAddConnection2")>]
  static extern int connect(NetResource netResource, string password, string username, int flags)
  [<DllImport(Mpr, EntryPoint = "WNetCancelConnection2")>]
  static extern int disconnect(string name, int flags, bool force)

  let mutable disposed = false;

  do
    let userName = if String.IsNullOrWhiteSpace credential.Domain
                   then credential.UserName
                   else credential.Domain + "\\" + credential.UserName
    let resource = new NetResource(networkName)

    let result = connect(resource, credential.Password, userName, 0)

    if result <> 0 then
      let msg = "Error connecting to remote share " + networkName
      new Win32Exception(result, msg)
      |> raise

  let cleanup(disposing:bool) =
    if not disposed then
      disposed <- true
      if disposing then () // TODO dispose managed resources here
      disconnect(networkName, 0, true) |> ignore

  interface IDisposable with
    member __.Dispose() =
      disconnect(networkName, 0, true) |> ignore
      GC.SuppressFinalize(__)

  override __.Finalize() = cleanup(false)

type CopyPath =
  | RemotePath of string * NetworkCredential
  | LocalPath of string

let createDisposable() =
  {
    new IDisposable with
      member __.Dispose() = ()
  }

let copyFile overwrite destPath srcPath : unit =
  use _srcConn =
    match srcPath with
    | RemotePath(path, credential) -> new WNetConnection(path, credential) :> IDisposable
    | LocalPath(_) -> createDisposable()
  use _destConn =
    match destPath with
    | RemotePath(path, credential) -> new WNetConnection(path, credential) :> IDisposable
    | LocalPath(_) -> createDisposable()
  match srcPath, destPath with
  | RemotePath(src, _), RemotePath(dest, _)
  | LocalPath(src), RemotePath(dest, _)
  | RemotePath(src, _), LocalPath(dest)
  | LocalPath(src), LocalPath(dest) ->
    if FileInfo(src).Exists |> not then
      failwith ("Source file not found: " + src)
    let destFilePath =
      if DirectoryInfo(dest).Exists then Path.Combine(dest, Path.GetFileName src)
      else dest
    File.Copy(src, destFilePath, overwrite)

let rec copyDir copySubDirs filePattern destPath srcPath =
  use _srcConn =
    match srcPath with
    | RemotePath(path, credential) -> new WNetConnection(path, credential) :> IDisposable
    | LocalPath(_) -> createDisposable()
  use _destConn =
    match destPath with
    | RemotePath(path, credential) -> new WNetConnection(path, credential) :> IDisposable
    | LocalPath(_) -> createDisposable()
  match srcPath, destPath with
  | RemotePath(src, _), RemotePath(dest, _)
  | LocalPath(src), RemotePath(dest, _)
  | RemotePath(src, _), LocalPath(dest)
  | LocalPath(src), LocalPath(dest) ->
    let dir = DirectoryInfo(src)
    if dir.Exists |> not then
      failwith ("Source directory not found: " + src)

    let dirs = dir.GetDirectories()
    if Directory.Exists(dest) |> not then
      Directory.CreateDirectory(dest) |> ignore

    let files = dir.GetFiles(filePattern)
    for file in files do
      let tempPath = Path.Combine(dest, file.Name)
      file.CopyTo(tempPath, false) |> ignore

    if copySubDirs then
      for subdir in dirs do
        let subdirSrc =
          match srcPath with
          | RemotePath(_, credential) -> RemotePath(Path.Combine(dest, subdir.Name), credential)
          | LocalPath(_) -> LocalPath(Path.Combine(dest, subdir.Name))
        let subdirDest =
          match destPath with
          | RemotePath(_, credential) -> RemotePath(subdir.FullName, credential)
          | LocalPath(_) -> LocalPath(subdir.FullName)
        copyDir copySubDirs filePattern subdirDest subdirSrc
python_kaa
  • 1,034
  • 13
  • 27