简体   繁体   中英

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

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?

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

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.

在此处输入图像描述

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

在此处输入图像描述

Step 3: Now click on Connect as..

在此处输入图像描述

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

在此处输入图像描述

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".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM