How can I use impersonation to manipulate files/directories on a remote machine with UNC?
Asked Answered
S

2

8

I need to download files from a server to a shared drive directory, creating the directory if it doesn't exist. There are a few things making this more complicated:

  1. I do not have write access (nor does the account that will run the job in UAT/Prod) to the shared drive directory.
  2. The Service account that does have write access does not have any privileges anywhere but the shared drive directory.

I attempt to impersonate, as so:

class Impersonation
{
    const int LOGON32_LOGON_NETWORK = 3;
    const int LOGON_TYPE_NEW_CREDENTIALS = 9;
    const int LOGON32_PROVIDER_WINNT50 = 3;

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

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

    public static void Impersonate(string domain, string user, string password, Action act)
    {
        //if no user specified, don't impersonate
        if (user.Trim() == "")
        {
            act();
            return;
        }
        WindowsImpersonationContext impersonationContext = null;
        IntPtr token = IntPtr.Zero;
        try
        {
            //if no domain specified, default it to current machine
            if (domain.Trim() == "")
            {
                domain = System.Environment.MachineName;
            }
            bool result = LogonUser(user, domain, password, LOGON_TYPE_NEW_CREDENTIALS, LOGON32_PROVIDER_WINNT50, ref token);
            WindowsIdentity wi = new WindowsIdentity(token);
            impersonationContext = WindowsIdentity.Impersonate(token);
            act();
        }
        catch (Exception ex)
        {
            if (impersonationContext != null)
            {
                impersonationContext.Undo();
                impersonationContext = null;
            }
            //if something went wrong, try it as the running user just in case
            act();
        }
        finally
        {
            if (impersonationContext != null)
            {
                impersonationContext.Undo();
                impersonationContext = null;
            }
            if (token != IntPtr.Zero)
            {
                CloseHandle(token);
                token = IntPtr.Zero;
            }
        }
    }
}

And a piece of the the actual calling code is (in another class):

private static void CreateDirectoryIfNotExist(string directory, string domain, string username, string password)
{
    Impersonation.Impersonate(domain, username, password, () => CreateIfNotExist(directory));
}

private static void CreateIfNotExist(string dir)
{
    if (!Directory.Exists(dir))
    {
        Directory.CreateDirectory(dir);
    }
}

If I run it with the proper login info for the service account, I get an Exception on the Directory.CreateDirectory(string) call:

{System.IO.IOException: This user isn't allowed to sign in to this computer.}

I'm guessing this means the service account isn't allowed to log in to the executing machine, which I already knew. But really, there's no reason it needs to log in to the executing machine. Is there a way I can use impersonation to log on to a remote machine and execute the commands from there?

Sleuth answered 2/10, 2019 at 18:48 Comment(2)
You first need to get pass windows credentials. So local and remote PCs have to be in the same group. The as a user you need an account on both local and remote machines.Connieconniption
As long as that other machine doesn't allow you to log in then you can't get anywhere. Ask IT staff to help you.Bleach
M
0

You cannot do it with impersonation if the account cannot log on. Impersonation requires the thread to run under the user credentials. That is why the LogonUser fails.

You can use the WNetAddConnection2 function that is used to establish a connection to a network resource.

Here is a sample for your CreateDirectoryIfNotExist function using this approach:

public static void CreateDirectoryIfNotExists(string directory, string sharePath, string username, string password)
{
   NETRESOURCE nr = new NETRESOURCE();
   nr.dwType = ResourceType.RESOURCETYPE_DISK;
   nr.lpLocalName = null;
   nr.lpRemoteName = sharePath;
   nr.lpProvider = null;

   int result = WNetAddConnection2(nr, password, username, 0);
   string directoryFullPath = Path.Combine(sharePath, directory);
   if (!Directory.Exists(directoryFullPath))
   {
      Directory.CreateDirectory(directoryFullPath);
   }
}

To be able to do the system call you need also the following definitions from pinvoke.net.

[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
   public ResourceScope dwScope = 0;
   public ResourceType dwType = 0;
   public ResourceDisplayType dwDisplayType = 0;
   public ResourceUsage dwUsage = 0;
   [MarshalAs(UnmanagedType.LPStr)] public string lpLocalName = null;
   [MarshalAs(UnmanagedType.LPStr)] public string lpRemoteName = null;
   [MarshalAs(UnmanagedType.LPStr)] public string lpComment = null;
   [MarshalAs(UnmanagedType.LPStr)] public string lpProvider;
};
public enum ResourceScope
{
   RESOURCE_CONNECTED = 1,
   RESOURCE_GLOBALNET,
   RESOURCE_REMEMBERED,
   RESOURCE_RECENT,
   RESOURCE_CONTEXT
};

public enum ResourceType
{
   RESOURCETYPE_ANY,
   RESOURCETYPE_DISK,
   RESOURCETYPE_PRINT,
   RESOURCETYPE_RESERVED
};

public enum ResourceUsage
{
   RESOURCEUSAGE_CONNECTABLE = 0x00000001,
   RESOURCEUSAGE_CONTAINER = 0x00000002,
   RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
   RESOURCEUSAGE_SIBLING = 0x00000008,
   RESOURCEUSAGE_ATTACHED = 0x00000010,
   RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
};

public enum ResourceDisplayType
{
   RESOURCEDISPLAYTYPE_GENERIC,
   RESOURCEDISPLAYTYPE_DOMAIN,
   RESOURCEDISPLAYTYPE_SERVER,
   RESOURCEDISPLAYTYPE_SHARE,
   RESOURCEDISPLAYTYPE_FILE,
   RESOURCEDISPLAYTYPE_GROUP,
   RESOURCEDISPLAYTYPE_NETWORK,
   RESOURCEDISPLAYTYPE_ROOT,
   RESOURCEDISPLAYTYPE_SHAREADMIN,
   RESOURCEDISPLAYTYPE_DIRECTORY,
   RESOURCEDISPLAYTYPE_TREE,
   RESOURCEDISPLAYTYPE_NDSCONTAINER
};
public enum ResourceConnection
{
   CONNECT_UPDATE_PROFILE = 1,
   CONNECT_UPDATE_RECENT = 2,
   CONNECT_TEMPORARY = 4,
   CONNECT_INTERACTIVE = 8,
   CONNECT_PROMPT = 0X10,
   CONNECT_REDIRECT = 0X80,
   CONNECT_CURRENT_MEDIA = 0X200,
   CONNECT_COMMAND_LINE = 0X800,
   CONNECT_CMD_SAVECRED = 0X1000,
   CONNECT_CRED_RESET = 0X2000

};

[DllImport("mpr.dll", CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
private static extern int WNetAddConnection2(NETRESOURCE lpNetResource,
      [MarshalAs(UnmanagedType.LPStr)]  string lpPassword,
      [MarshalAs(UnmanagedType.LPStr)]  string lpUserName, int dwFlags);

You can add this definitions to the same class as your function.

Here are also links to two older post which also use the same approach.

Accessing a Shared File (UNC) From a Remote, Non-Trusted Domain With Credentials

How do I access a file share programmatically

Menstrual answered 17/10, 2019 at 22:33 Comment(2)
Well now I have a slightly different error. 1208 ERROR_EXTENDED_ERROR. Per WNetGetLastError, "The user is not allowed to log on from this workstation."Sleuth
Try to connect to the share in windows explorer from a user on the server that has no permission to the share at all. This should give you a prompt to login and then you can use the service account to login and see if it works. If this does not work the problem is in the security or network settings and not in the code.Saint
C
0

You can solve this problem like I solved it. Below are the steps:

Step 1: Open IIS. Then add a virtual directory in your web application.

enter image description here

Step 2: Then map your shared path with the virtual directory added in Step 1

enter image description here

Step 3: Now click on Connect as..

enter image description here

Step 4: Set the credential of your desired user by which you want to connect your shared path.

enter image description here

Step 5: Now the IIS configuration part is over and you need to start using src="Image/Image.png" in your html or start manipulating "SharedFolder/YourFile.ext".

Crystacrystal answered 24/10, 2019 at 5:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.