Using .net 4.0 in a console application
I am trying to move files from local computer to a shared drive and this code gives me the error at the using statement.
The credentials are valid. What could be going wrong.
namespace UploadList
{
class Program
{
static void Main(string[] args)
{
MoveFiles();
}
public static void MoveFiles()
{
try
{
NetworkCredential readCredentials = new
NetworkCredential(#"gktrs\PRDSys", "P#ssw0rd123");
string filepath = "\\\\10.60.90.1\\D$";
//error at the using statement
//ERROR : *Error connecting to remote share*
using (new NetworkConnection(filepath, readCredentials))
{
}
}
catch(Exception ex)
{
}
}
}
}
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 result = WNetAddConnection2(
netResource,
credentials.Password,
credentials.UserName,
0);
if (result != 0)
{
throw new Win32Exception(result, "Error connecting to remote share");
}
}
~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;
public string LocalName;
public string RemoteName;
public string Comment;
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
}
Here's something I cobbled together from other Stack overflow answers to solve a similar problem of reading a file on a share in MVC. basically C# dosnt make it easy
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace UnitTestProject2
{
/// <summary>
/// MVC 5 dosnt support impersonation via web.config for good reasons microsoft decided one day
/// </summary>
internal static class NativeMethods
{
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);
}
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true)
{
}
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(handle);
}
}
class Class6
{
public void readfile(string filename, string Username, string DomainName, string Password)
{
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
SafeTokenHandle safeTokenHandle;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = NativeMethods.LogonUser(
Username,
DomainName,
Password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
if (!returnValue)
{
throw new Exception("unable to login as specifed user :" + Username);
}
using (WindowsIdentity id = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
using (WindowsImpersonationContext wic = id.Impersonate())
{
if (File.Exists(filename))
{
//good to go
}
}
}
}
}
Related
I have scenario of printing pdf (generated from stream) to network printer through application hosted in IIS. I tried with PrintDocument.Print() and problem I'm facing is: 1. Document is getting queued to the print job queue with size 0 bytes. 2. Document is getting queued to the print job queue with owner name as machine_name.
Here is the code which i tried using PdfiumViewer (to generate PrintDocument from bytearray) and System.Drawing.Printing.PrintDocument:
public void SendPdfToPrinter(byte[] byteArray, string fileName, string printerNetworkPath)
{
using (Stream fileStream = new MemoryStream(byteArray)) //byte array for the file content
{
var printerSettings = new System.Drawing.Printing.PrinterSettings
{
PrinterName = printerNetworkPath, //this is the printer full name. i.e. \\10.10.0.12\ABC-XEROX-01
PrintFileName = fileName, //file name. i.e. abc.pdf
PrintRange = System.Drawing.Printing.PrintRange.AllPages,
};
printerSettings.DefaultPageSettings.Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0);
// Now print the PDF document
using (PdfiumViewer.PdfDocument document = PdfiumViewer.PdfDocument.Load(fileStream))
{
using (System.Drawing.Printing.PrintDocument printDocument = document.CreatePrintDocument())
{
printDocument.DocumentName = fileName;
printDocument.PrinterSettings = printerSettings;
printDocument.PrintController = new System.Drawing.Printing.StandardPrintController();
printDocument.Print();
}
}
For both the problems, answer is impersonating the user to do the printing.
In my case app pool is running under LocalSystem account which is obviously is not a domain user and printer is exposed to the domain user only.
Note: Application pool is 64bit, if you use 32bit you will face another set of challenges which is well described here:
https://blogs.msdn.microsoft.com/winsdk/2015/05/19/printing-successfully-using-impersonation-from-a-32-bit-application-on-a-64-bit-system/
Below is code which required to do the impersonation for domain user:
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonation : IDisposable
{
private readonly SafeTokenHandle _handle;
private readonly WindowsImpersonationContext _context;
bool disposed = false;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
public Impersonation(ImpersonateUserDetails user) : this(user.Domain, user.UserName, user.Password)
{ }
public Impersonation(string domain, string username, string password)
{
var ok = LogonUser(username, domain, password,
LOGON32_LOGON_INTERACTIVE, 0, out this._handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
this._context = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle());
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
this._context.Dispose();
this._handle.Dispose();
}
disposed = true;
}
~Impersonation()
{
Dispose(false);
}
[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);
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 ImpersonateUserDetails
{
public string UserName { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
}
Another possible & easy solution is to configure your Application Pool Identity to a custom/domain user which has access/permission to print in network printers.
I have been using this link to setup impersonation. I have many methods and i need to impersonate for all of them.
Do i have to add the using code around every method call or is there
a way to set impersonation for the entire lifecycle of the
application ?
I am impersonating a local administrator below - are
there any flaws or security implications with my code ?
is the LOGON32_LOGON_NEW_CREDENTIALS value of 2 correct below (it is working as expected)
c# Code
private void buttonUsingImpersonation_Click(object sender, EventArgs e)
{
try
{
using (new Impersonation("LocalHost", "test", "test"))
{
// do whatever you want
string fileName = System.IO.Path.GetRandomFileName();
String pathString = System.IO.Path.Combine(FolderPath, fileName);
.
if (!System.IO.File.Exists(pathString))
{
using (System.IO.FileStream fs = System.IO.File.Create(pathString))
{
for (byte i = 0; i < 100; i++)
{
fs.WriteByte(i);
}
}
}
else
{
MessageBox.Show("File already exists " + fileName);
return;
}
}
}
catch (Exception ex)
{
// If exception happens, it will be returned here
MessageBox.Show("Error " + MethodBase.GetCurrentMethod().Name + " " + ex.ToString());
}
}
Class
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 ImpersonationDemo
{
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonation : IDisposable
{
private readonly SafeTokenHandle _handle;
private readonly WindowsImpersonationContext _context;
const int LOGON32_LOGON_NEW_CREDENTIALS = 2;
public Impersonation(string domain, string username, string password)
{
var ok = LogonUser(username, domain, password,
LOGON32_LOGON_NEW_CREDENTIALS, 0, out this._handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
this._context = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle());
}
public void Dispose()
{
this._context.Dispose();
this._handle.Dispose();
}
[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);
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);
}
}
}
}
There are many ways of doing this, not really a single correct way. One way could be that you pass the methods you want impersonated as a delegate, to some kind of ImpersonatedContext class, that will wrap the invocation in your Impersonator scope. This will also move all the logic away from the button click event handler, which is also architecturally not the best way.
Regarding your question about security, then as of now you are specifying the username and password directly. If Eve was to decompile your code, she would be able to see the password. Maybe you should let the user specify administrators password?
I have been using the example code as well, and it has worked fine for me. I would think that LOGON32_LOGON_NEW_CREDENTIALS is fine. You can read more about the different modes here (LogonUser)
Update
I guess a quick sample of what that would look like, is something like this. The delegate you pass accepts an object, and returns a success value as a boolean. You could modify that to suit your needs.
public class ImpersonationContext {
public delegate bool ImpersonationDel(object obj);
public bool Invoke(ImpersonationDel del){
using (new Impersonation("LocalHost", "test", "test")){
return del.Invoke();
}
}
}
I have ASP.Net and C# application. I am uploading images to the site and store them in the C:\Images directory, which works fine. When I save images to the C:\Images folder and simultaneously copy (or some times move) to the shared drive, I use the shared drive physical address, which looks like \\192.xxx.x.xx\some folder\Images. This drive is mapped to the deployment server. I am using IIS hosting for the site.
The problem is with the shared drive copying. When I use the site from local machine (where the site is deployed) that copies the file to the shared drive. But when I use the site from another machine (other than the deployed server) that saves the image in C:\Images, but it won't copy the file to the shared drive.
Here's the code I'm using
**Loggedon method shows success in debug.
public static void CopytoNetwork(String Filename)
{
try
{
string updir = System.Configuration.ConfigurationManager.AppSettings["PhysicalPath"].ToString();
WindowsImpersonationContext impersonationContext = null;
IntPtr userHandle = IntPtr.Zero;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
String UserName = System.Configuration.ConfigurationManager.AppSettings["Server_UserName"].ToString();
String Password = System.Configuration.ConfigurationManager.AppSettings["server_Password"].ToString();
String DomainName = System.Configuration.ConfigurationManager.AppSettings["Server_Domain"].ToString();
bool loggedOn = LogonUser(UserName, DomainName, Password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref userHandle);
try
{
File.Move(#"C:\Images\" + Filename, updir + "\\" + Filename);
}
catch (Exception)
{
}
finally
{
if (impersonationContext != null)
{
impersonationContext.Undo();
}
if (userHandle != IntPtr.Zero)
{
CloseHandle(userHandle);
}
}
}
catch (Exception)
{
}
}
You can set up an impersonated user class like this:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
public class ImpersonatedUser : IDisposable
{
IntPtr userHandle;
WindowsImpersonationContext impersonationContext;
public ImpersonatedUser(string user, string domain, string password)
{
userHandle = IntPtr.Zero;
bool loggedOn = LogonUser(
user,
domain,
password,
LogonType.Interactive,
LogonProvider.Default,
out userHandle);
if (!loggedOn)
throw new Win32Exception(Marshal.GetLastWin32Error());
// Begin impersonating the user
impersonationContext = WindowsIdentity.Impersonate(userHandle);
}
public void Dispose()
{
if (userHandle != IntPtr.Zero)
{
CloseHandle(userHandle);
userHandle = IntPtr.Zero;
impersonationContext.Undo();
}
}
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
LogonType dwLogonType,
LogonProvider dwLogonProvider,
out IntPtr phToken
);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hHandle);
enum LogonType : int
{
Interactive = 2,
Network = 3,
Batch = 4,
Service = 5,
NetworkCleartext = 8,
NewCredentials = 9,
}
enum LogonProvider : int
{
Default = 0,
}
}
When you need to do the file copying you do it like this:
using (new ImpersonatedUser(<UserName>, <UserDomainName>, <UserPassword>))
{
DoYourFileCopyLogic();
}
I have to create many files in Network drive with specified user.
I used this answer to connect different user
I use Impersonator Class :
public class Impersonator : IDisposable
{
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
private IntPtr token = IntPtr.Zero;
private WindowsImpersonationContext impersonated;
private readonly string _ErrMsg = "";
public bool IsImpersonating
{
get { return (token != IntPtr.Zero) && (impersonated != null); }
}
public string ErrMsg
{
get { return _ErrMsg; }
}
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public Impersonator(string userName, string password, string domain)
{
StopImpersonating();
bool loggedOn = LogonUser(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token);
if (!loggedOn)
{
_ErrMsg = new System.ComponentModel.Win32Exception().Message;
return;
}
WindowsIdentity identity = new WindowsIdentity(token);
impersonated = identity.Impersonate();
}
private void StopImpersonating()
{
if (impersonated != null)
{
impersonated.Undo();
impersonated = null;
}
if (token != IntPtr.Zero)
{
CloseHandle(token);
token = IntPtr.Zero;
}
}
public void Dispose()
{
StopImpersonating();
}
}
and the code :
using (Impersonator impersonator = new Impersonator("UserName", "UserPwd", "UserDomaine"))
{
if (!Directory.Exists("Z:\\")) // check if Network drive exist
{
NetworkDrive drive = new NetworkDrive
{
ShareName = #"\\IP\Partage",
LocalDrive = "Z",
Force = true
};
drive.MapDrive(#"UserDomaine\UserName", "UserPwd");
}
File.Create(#"Z:\Log\FileName.txt");
}
But in this case I found that the code Map the drive every time that I have to create a file or update it!! And I have a lot of work with this function.
There’s a solution to not map it every time?
I tried to Map the driver in with this user in opening of application but same problem.
I think you don't need to map the drive. After impersonating you can just create the file directly using the network drive and it will create the file as impersonated user.
using (Impersonator impersonator = new Impersonator("UserName", "UserPwd", "UserDomaine"))
{
File.Create(#"\\IP\Partage\Log\FileName.txt");
}
Try not to use Using block. declare Impersonator as global static variable.
I tried to upload file to a network path with following codes for silverlight app:
public void ProcessRequest (HttpContext context)
{
//.....
using (FileStream fs = File.Create(#"\\Server\Folder\" + filename))
{
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = context.Request.InputStream.Read(buffer, 0, buffer.Length)) != 0)
{
fs.Write(buffer, 0, bytesRead);
}
}
}
It's working fine when I run it in debug mode with VS built-in web server. At SL side, I call this handler with url like:
UriBuilder ub = new UriBuilder("http://localhost:38700/FileUpload.ashx");
Then I publish this app to local IIS and change the url to http://localhost/Mysite/FileUpload.ashx
then run the app again. It won't work anymore, but no error.
I guess it is because different credential to call File.Create. So I want to use specific credential in handler to put file to the destination.
How to use a credential for File.Create?
I believe you will need to impersonate a user. The code below should do it. Essentially, you gather the domain, user, and password and instantiate the ImpersonationMgr class. Then call the BeginImpersonation method. After that call the corresponding WriteFile method you wish. The WriteFile method will assert if impersonation is enabled. You can follow similar patterns for other file methods such as delete and move.
public class ImpersonationMgr : IDisposable
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType,
int dwLogonProvider, out SafeTokenHandle phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
private const int LOGON32_PROVIDER_DEFAULT = 0;
private const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
private readonly string userName = string.Empty;
private readonly string domainName = string.Empty;
private readonly string password = string.Empty;
private SafeTokenHandle safeTokenHandle = null;
private WindowsImpersonationContext impersonatedUser = null;
public ImpersonationMgr(string userName, string domainName, string password)
{
this.userName = userName;
this.domainName = domainName;
this.password = password;
}
public void BeginImpersonation()
{
bool returnValue = LogonUser(userName, domainName, password, LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_DEFAULT, out safeTokenHandle);
if (returnValue == false)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
impersonatedUser = WindowsIdentity.Impersonate(safeTokenHandle.DangerousGetHandle());
}
private void AssertImpersonationIsEnabled()
{
if(safeTokenHandle == null || impersonatedUser == null)
{
throw new UnauthorizedAccessException("You must call the BeginImpersonation method before attempting file write access.");
}
}
public void WriteFile(string pathToFile, string fileContents)
{
AssertImpersonationIsEnabled();
using (FileStream fileStream = File.Open(pathToFile, FileMode.CreateNew))
{
using (StreamWriter fileWriter = new StreamWriter(fileStream))
{
fileWriter.Write(fileContents);
}
}
}
public void WriteFile(string pathToFile, byte[] fileContents)
{
AssertImpersonationIsEnabled();
using (FileStream fileStream = new FileStream(pathToFile, FileMode.Create))
{
fileStream .Write(fileContents, 0, fileContents.Length);
fileStream .Flush();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
UPDATE
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);
}
}