How to provide user name and password when connecting to a network share
Asked Answered
A

13

224

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.
Ascensive answered 17/11, 2008 at 13:7 Comment(2)
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. :-/Discriminatory
Related - #17786537Huan
E
163

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");
}
Exhibit answered 17/11, 2008 at 14:39 Comment(6)
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.Leach
@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?Galla
@Galla - 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).Exhibit
@MarkBrackett I meanwhile tested it and your expectation was correct.Galla
The definitions of readCredentials and writeCredentials could be included in the answer.Fitzpatrick
If you're getting Error 53, make sure the path isn't ending with a "\"Punctuation
F
362

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
}
Ferland answered 29/7, 2009 at 0:10 Comment(25)
It really should be throw new Win32Exception(result);, since WNetAddConnection2 returns win32 error codes (ERROR_XXX)Tansy
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.Vantassel
Can you explain where does ResourceScope.Recent and ResourceScope.Context come from? Since dwScope seems to have only 3 values possible (as written here): RESOURCE_CONNECTED, RESOURCE_GLOBALNET, RESOURCE_REMEMBERED. Also, where does ResourceType.Reserved comes from?Colyer
I got those structures from pinvoke.net here and here. If you search around for RESOURCE_RECENT and RESOURCETYPE_RESERVED there are a few reactos hits so perhaps they are undocumented values?Ferland
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.Normalie
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!!!Garwin
Nice class. I just gave a link to this anwer for accessing a remote registry with credentials: https://mcmap.net/q/58111/-accessing-a-remote-registry-with-local-credentials-of-the-remote-machine-on-the-domainRossi
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?Emden
The following using statements are required in order for the code above to compile: using System.Net; using System.Runtime.InteropServices; using System.ComponentModel;Berenice
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?Beane
We had the same problem as you, @Beane . 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.Crossbow
According to the documentation of WNetAddConnection2, the Scope and DisplayType values are ignored, so there's no need to set them.Religiosity
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?Fulviah
You may want to add networkName-parameter validation, or autofix this using something like this: ` _networkName = networkName.TrimEnd(System.IO.Path.DirectorySeparatorChar); `Derm
@LukeQuinane i removed message part from Win32Exception that way the exception.Message will become the actual message from the error code.Newman
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.Opportunity
@Opportunity "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.Gondi
@TomLint yes, actually after posting this comment I opened a question (#52644317) to get more details on that point, and I got them :)Opportunity
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 #1364179 and helped do some proper path verification.Mccammon
@LukeQuinane - You should create a NuGet package for this. That would be a great help.Ooze
Brilliant code. I had a shared folder on an Azure server and had to copy files locally. This worked beautifully.Thimblerig
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 ?Rivy
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)Eiland
According to DllImportAttribute.CharSet: _ The default enumeration member for C# and Visual Basic is CharSet.Ansi_. Also see Specify a character set.Collative
According to Default marshalling for strings: 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'Collative
E
163

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");
}
Exhibit answered 17/11, 2008 at 14:39 Comment(6)
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.Leach
@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?Galla
@Galla - 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).Exhibit
@MarkBrackett I meanwhile tested it and your expectation was correct.Galla
The definitions of readCredentials and writeCredentials could be included in the answer.Fitzpatrick
If you're getting Error 53, make sure the path isn't ending with a "\"Punctuation
I
64

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;
            }
        }
    }
}
Interviewee answered 16/9, 2016 at 21:39 Comment(6)
@MohammadRashid According to the documentation on LogonUser, 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.Irrespirable
@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.Snatchy
This is A GREAT SOLUTION! Thank you, Pavel Kovalev.Mongrel
does this work on ldap? it says that i don't have a logon server available. im using ldap authDaisey
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.Ferretti
@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.Interviewee
H
29

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.

Hardball answered 25/11, 2011 at 16:20 Comment(2)
In case the share mapping fails, what would the return codes be?Footman
github.com/Tyrrrz/CliWrap can make this more reliable.Burnsides
N
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();
Nolen answered 18/3, 2014 at 13:55 Comment(1)
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...Raama
S
7

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
Shellishellie answered 5/11, 2015 at 15:42 Comment(1)
Thanks. Could you please add a VB.NET usage example?Caterpillar
F
7

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.

Fijian answered 18/9, 2022 at 13:16 Comment(1)
github.com/Tyrrrz/CliWrapBurnsides
C
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).

Colossus answered 17/11, 2008 at 13:18 Comment(3)
i don't think the process thing is such a bad idea. google put out some whitepapers about the benefits of multiprocessing in chrome.Overslaugh
Is it possible to change the thread principal to an user with no account on the local machine?Ascensive
To be honest, I simply don't know... You'd have to try LogonUser with a different domain to find out.Colossus
S
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!!!

Sible answered 30/1, 2010 at 8:12 Comment(0)
S
2

You should be looking at adding a line like this:

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

Into your web.config.

More Information.

Shannashannah answered 17/11, 2008 at 13:12 Comment(1)
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.Rateable
L
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

Leach answered 17/11, 2008 at 14:35 Comment(0)
A
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
Agha answered 10/11, 2015 at 16:22 Comment(0)
B
0

All the answers here deal with plaintext passwords being held in a variable, and in memory. Where do these passwords come from? Settings files, or environment variables?

The best solution is a mixture of things already said in other answers but step one is to run cmdkey.exe manually as the user who will be running the process. So if this is a Windows Service then you want to run a cmd.exe as the service account user.

cmdkey.exe /add:{serverName} /user:{userName} /pass:{password}

This stores the credential in the Windows Credential Manager, which doesn't store the password in plaintext. The password only existed in plaintext during that command terminal session which is now over.

Once the credential is stored you can use NET USE to attach to that server using the stored credential.

Here is an example using CliWrap

private static readonly Command Net = Cli.Wrap("net.exe");

public static async Task NetUseAsync(string fullPath)
{
    var errorOutput = new StringBuilder();
    try
    {       
        var errorPipe = PipeTarget.ToStringBuilder(errorOutput);
        await Net.WithArguments($"USE {fullPath}")
            .WithStandardOutputPipe(errorPipe)
            .WithStandardErrorPipe(errorPipe)
            .ExecuteAsync();
    }
    catch (CommandExecutionException e)
    {
        throw new InvalidOperationException(errorOutput.ToString(), e)
        {
            HResult = e.ExitCode
        };
    }
}

Now there are no plaintext passwords in settings files, environment variables, or in memory.

Burnsides answered 6/2 at 21:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.