We're trying to implement MSMQ in our applications and we face the next problem: Our application(console application) starts under a local machine (machine1) user account in the domain X.
On the same domain there is another machine (machine2) and on this machine is the queue. On the domain X there is an user account with admin rights and this user has full control on the queue, but when our application starts, because it runs under the local account, it doesn't have the rights to read messagess.
Is there any solution on how to fix this issue only from the code? We can't change the user account that our console application is using. I'm thinking to use impersonation as the last solution.
Do you have any solution for this problem?
Both private and public queues should allow you to do this: https://technet.microsoft.com/en-us/library/cc772532.aspx
Have a look at the section under Public and Private queues.
Public queues should be available to any domain account, including machine accounts.
Private queues allow anyone to write, but need particular permission to read.
I've been able to read messages only by impersonating. Here is my code:
Impersonating context wrapper:
public class WrapperImpersonationContext
{
[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 const int LOGON32_PROVIDER_DEFAULT = 0;
private const int LOGON32_LOGON_INTERACTIVE = 2;
private string m_Domain;
private string m_Password;
private string m_Username;
private IntPtr m_Token;
private WindowsImpersonationContext m_Context = null;
protected bool IsInContext
{
get { return m_Context != null; }
}
public WrapperImpersonationContext(string domain, string username, string password)
{
m_Domain = domain;
m_Username = username;
m_Password = password;
}
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Enter(out string result)
{
if (this.IsInContext)
{
result = "not in context";
return;
}
m_Token = new IntPtr(0);
try
{
m_Token = IntPtr.Zero;
bool logonSuccessfull = LogonUser(
m_Username,
m_Domain,
m_Password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
ref m_Token);
if (logonSuccessfull == false)
{
result = "logon failed";
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error);
}
else
{
result = "logon succseeded";
}
WindowsIdentity identity = new WindowsIdentity(m_Token);
m_Context = identity.Impersonate();
}
catch (Exception exception)
{
result = "exception: " + exception.Message;
// Catch exceptions here
}
}
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Leave()
{
if (this.IsInContext == false) return;
m_Context.Undo();
if (m_Token != IntPtr.Zero) CloseHandle(m_Token);
m_Context = null;
}
}
Reading messages:
MessageQueue queue = new MessageQueue(#"FormatName:DIRECT=OS:servername\PRIVATE$\queue_name");
WrapperImpersonationContext context = new WrapperImpersonationContext("domain", "username", "password");
context.Enter(out result);
Message msg = queue.Receive();
context.Leave();
If you can't impersonate a valid domain account, you would have to allocate the 'anonymous logon' special account permissions on the queue. The 'everyone' special group won't work as that only covers accounts recognised by the domain. Accounts local to your machine are foreign to the domain and not included.
Related
I need to be able to programmatically authenticate when trying to read and write files on a remote computer in a non-domain environment.
When you type a command into the Windows RUN prompt that is similar to \\targetComputer\C$\targetFolder or \\targetComputer\admin$, where the targetComputer is NOT on a domain, you will be prompted to enter a username and password. Once you enter the username and password, you have full access to the remote folder.
How can I accomplish this authentication programmatically in C#?
I've tried..
--Impersonation, but it appears to only work in a domain environment.
--CMDKEY.exe, but it also seems to only work in a domain environment.
There must be a way to do this, but I have searched high and low with no luck so far. Maybe I'm just looking for the wrong thing? I'm sure I'm not the first to have this question. Any help would be greatly appreciated.
Thanks!
EDIT :
I think I just found a different SO posting that answers my question: Accessing a Shared File (UNC) From a Remote, Non-Trusted Domain With Credentials
I will work with that for now and see where it gets me.
Thanks!
Impersonation works with Peer/LAN network as well. I got your typical home network with some machines on default "Workgroup" and some on a named one if I remembered doing it on the install.
Here is the code I use from my IIS server app to access files on my other computer (without having to have the same user and password on both machines involved, copied from somewhere and modified for my use):
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.ComponentModel;
/// <summary>
/// Class to impersonate another user. Requires user, pass and domain/computername
/// All code run after impersonationuser has been run will run as this user.
/// Remember to Dispose() afterwards.
/// </summary>
public class ImpersonateUser:IDisposable {
private WindowsImpersonationContext LastContext = null;
private IntPtr LastUserHandle = IntPtr.Zero;
#region User Impersonation api
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ImpersonateLoggedOnUser(int Token);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DuplicateToken(IntPtr token, int impersonationLevel, ref IntPtr duplication);
[DllImport("kernel32.dll")]
public static extern Boolean CloseHandle(IntPtr hObject);
public const int LOGON32_PROVIDER_DEFAULT = 0;
public const int LOGON32_PROVIDER_WINNT35 = 1;
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_LOGON_NETWORK = 3;
public const int LOGON32_LOGON_BATCH = 4;
public const int LOGON32_LOGON_SERVICE = 5;
public const int LOGON32_LOGON_UNLOCK = 7;
public const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;// Win2K or higher
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;// Win2K or higher
#endregion
public ImpersonateUser(string username, string domainOrComputerName, string password, int nm = LOGON32_LOGON_NETWORK) {
IntPtr userToken = IntPtr.Zero;
IntPtr userTokenDuplication = IntPtr.Zero;
bool loggedOn = false;
if (domainOrComputerName == null) domainOrComputerName = Environment.UserDomainName;
if (domainOrComputerName.ToLower() == "nt authority") {
loggedOn = LogonUser(username, domainOrComputerName, password, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, out userToken);
} else {
loggedOn = LogonUser(username, domainOrComputerName, password, nm, LOGON32_PROVIDER_DEFAULT, out userToken);
}
WindowsImpersonationContext _impersonationContext = null;
if (loggedOn) {
try {
// Create a duplication of the usertoken, this is a solution
// for the known bug that is published under KB article Q319615.
if (DuplicateToken(userToken, 2, ref userTokenDuplication)) {
// Create windows identity from the token and impersonate the user.
WindowsIdentity identity = new WindowsIdentity(userTokenDuplication);
_impersonationContext = identity.Impersonate();
} else {
// Token duplication failed!
// Use the default ctor overload
// that will use Mashal.GetLastWin32Error();
// to create the exceptions details.
throw new Win32Exception();
}
} finally {
// Close usertoken handle duplication when created.
if (!userTokenDuplication.Equals(IntPtr.Zero)) {
// Closes the handle of the user.
CloseHandle(userTokenDuplication);
userTokenDuplication = IntPtr.Zero;
}
// Close usertoken handle when created.
if (!userToken.Equals(IntPtr.Zero)) {
// Closes the handle of the user.
CloseHandle(userToken);
userToken = IntPtr.Zero;
}
}
} else {
// Logon failed!
// Use the default ctor overload that
// will use Mashal.GetLastWin32Error();
// to create the exceptions details.
throw new Win32Exception();
}
if (LastContext == null) LastContext = _impersonationContext;
}
public void Dispose() {
LastContext.Undo();
LastContext.Dispose();
}
}
The specific code I found out worked after a bit of trying was this:
using (var impersonation = new ImpersonateUser("OtherMachineUser", "OtherMachineName", "Password", LOGON32_LOGON_NEW_CREDENTIALS))
{
var files = System.IO.Directory.GetFiles("\\OtherMachineName\fileshare");
}
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.
It is possible to use the logonuser function for logging onto a domain.
http://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx
I want to logon programatically from C# onto a windows machine which is not part of any domain. How to achieve this?
I am using the following Program to logon :
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
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);
internal void validateusercredentials(string username, string password, string hostname)
{
assert.isnotnull(username);
intptr tokenhandle = new intptr(0);
windowsidentity windowsid = null;
try
{
const int logon32_provider_default = 0;
const int logon32_logon_network = 3;
tokenhandle = intptr.zero;
bool success = logonuser(username, ".", password, logon32_logon_network,
logon32_provider_default, ref tokenhandle);
console.writeline("the return value of logon user is " + success);
if (!success)
{
int lastwindowserror = marshal.getlastwin32error();
if (lastwindowserror == error_logon_failure)
{
string message = string.format("invalid credentials supplied for user {0}", username);
console.writeline(lastwindowserror);
throw new invalidcredentialexception(message);
}
}
}
catch (exception e)
{
console.writeline(e.message);
trace.traceerror(e.message);
throw;
}
finally
{
if (tokenhandle != intptr.zero)
{
closehandle(tokenhandle);
}
if (windowsid != null)
{
windowsid.dispose();
windowsid = null;
}
}
}
If the machine is not part of your domain, you cannot use your domain credentials.
If you have a local account, you can instead use the local name of the computer as domain name and your local user and password as user and password.
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.