Failure with LogonUser in MC++
- by Alikar
After fighting with this for a week I have not really gotten anywhere in why it constantly fails in my code, but not in other examples. My code, which while it compiles, will not log into a user that I know has the correct login information. Where it fails is the following line:
wi = gcnew WindowsIdentity(token);
It fails here because the token is zero, meaning that it was never set to a user token. Here is my full code:
#ifndef UNCAPI_H
#define UNCAPI_H
#include <windows.h>
#pragma once
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Security::Principal;
using namespace System::Security::Permissions;
namespace UNCAPI
{
public ref class UNCAccess
{
public:
	//bool Logon(String ^_srUsername, String ^_srDomain, String ^_srPassword);
	[PermissionSetAttribute(SecurityAction::Demand, Name = "FullTrust")]
	bool Logon(String ^_srUsername, String ^_srDomain, String ^_srPassword)
	{
		bool bSuccess = false;
		token = IntPtr(0);
		bSuccess = LogonUser(_srUsername, _srDomain, _srPassword, 8, 0, &tokenHandle);
		if(bSuccess)
		{
			wi = gcnew WindowsIdentity(token);
			wic = wi->Impersonate();                
		}
		return bSuccess;
	}
	void UNCAccess::Logoff()
	{
		if (wic != nullptr )
		{
			wic->Undo();
		}
		CloseHandle((int*)token.ToPointer());			
	}
private:
	[DllImport("advapi32.dll", SetLastError=true)]//[DllImport("advapi32.DLL", EntryPoint="LogonUserW",  SetLastError=true, CharSet=CharSet::Unicode, ExactSpelling=true, CallingConvention=CallingConvention::StdCall)]
    bool static LogonUser(String ^lpszUsername, String ^lpszDomain, String ^lpszPassword, int dwLogonType, int dwLogonProvider, IntPtr *phToken);
	[DllImport("KERNEL32.DLL", EntryPoint="CloseHandle",  SetLastError=true, CharSet=CharSet::Unicode, ExactSpelling=true, CallingConvention=CallingConvention::StdCall)]
    bool static CloseHandle(int *handle);   
	IntPtr token;
    WindowsIdentity ^wi;
    WindowsImpersonationContext ^wic;
};// End of Class UNCAccess
}// End of Name Space
#endif UNCAPI_H
Now using this slightly modified example from Microsoft I was able to get a login and a token:
#using <mscorlib.dll>
#using <System.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Security::Principal;
using namespace System::Security::Permissions;
[assembly:SecurityPermissionAttribute(SecurityAction::RequestMinimum, UnmanagedCode=true)]
[assembly:PermissionSetAttribute(SecurityAction::RequestMinimum, Name = "FullTrust")];
[DllImport("advapi32.dll", SetLastError=true)]
bool LogonUser(String^ lpszUsername, String^ lpszDomain, String^ lpszPassword, int dwLogonType, int dwLogonProvider, IntPtr* phToken);
[DllImport("kernel32.dll", CharSet=System::Runtime::InteropServices::CharSet::Auto)]
int FormatMessage(int dwFlags, IntPtr* lpSource, int dwMessageId, int dwLanguageId, String^ lpBuffer, int nSize, IntPtr *Arguments);
[DllImport("kernel32.dll", CharSet=CharSet::Auto)]
bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet=CharSet::Auto, SetLastError=true)]
bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, IntPtr* DuplicateTokenHandle);
// GetErrorMessage formats and returns an error message
// corresponding to the input errorCode.
String^ GetErrorMessage(int errorCode)
{
    int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
    int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
    int FORMAT_MESSAGE_FROM_SYSTEM  = 0x00001000;
    //int errorCode = 0x5; //ERROR_ACCESS_DENIED
    //throw new System.ComponentModel.Win32Exception(errorCode);
    int messageSize = 255;
    String^ lpMsgBuf = "";
    int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
    IntPtr ptrlpSource = IntPtr::Zero;
    IntPtr prtArguments = IntPtr::Zero;
    int retVal = FormatMessage(dwFlags, &ptrlpSource, errorCode, 0, lpMsgBuf, messageSize, &prtArguments);
    if (0 == retVal)
    {
        throw gcnew Exception(String::Format( "Failed to format message for error code {0}. ", errorCode));
    }
    return lpMsgBuf;
}
// Test harness.
// If you incorporate this code into a DLL, be sure to demand FullTrust.
[PermissionSetAttribute(SecurityAction::Demand, Name = "FullTrust")]
int main()
{    
    IntPtr tokenHandle = IntPtr(0);
    IntPtr dupeTokenHandle = IntPtr(0);
    try
    {
        String^ userName;
        String^ domainName;
        // Get the user token for the specified user, domain, and password using the 
        // unmanaged LogonUser method.  
        // The local machine name can be used for the domain name to impersonate a user on this machine.
        Console::Write("Enter the name of the domain on which to log on: ");
        domainName = Console::ReadLine();
        Console::Write("Enter the login of a user on {0} that you wish to impersonate: ", domainName);
        userName = Console::ReadLine();
        Console::Write("Enter the password for {0}: ", userName);
        const int LOGON32_PROVIDER_DEFAULT = 0;
        //This parameter causes LogonUser to create a primary token.
        const int LOGON32_LOGON_INTERACTIVE = 2;
        const int SecurityImpersonation = 2;
        tokenHandle = IntPtr::Zero;
        dupeTokenHandle = IntPtr::Zero;
        // Call LogonUser to obtain a handle to an access token.
        bool returnValue = LogonUser(userName, domainName, Console::ReadLine(), 
        LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
        &tokenHandle);
        Console::WriteLine("LogonUser called.");
        if (false == returnValue)
        {
            int ret = Marshal::GetLastWin32Error();
            Console::WriteLine("LogonUser failed with error code : {0}", ret);
            Console::WriteLine("\nError: [{0}] {1}\n", ret, GetErrorMessage(ret));
            int errorCode = 0x5; //ERROR_ACCESS_DENIED
            throw gcnew System::ComponentModel::Win32Exception(errorCode);
        }
        Console::WriteLine("Did LogonUser Succeed? {0}", (returnValue?"Yes":"No"));
        Console::WriteLine("Value of Windows NT token: {0}", tokenHandle);
        // Check the identity.
        Console::WriteLine("Before impersonation: {0}", WindowsIdentity::GetCurrent()->Name);
        bool retVal = DuplicateToken(tokenHandle, SecurityImpersonation, &dupeTokenHandle);
        if (false == retVal)
        {
            CloseHandle(tokenHandle);
            Console::WriteLine("Exception thrown in trying to duplicate token.");        
            return -1;
        }
        // The token that is passed to the following constructor must 
        // be a primary token in order to use it for impersonation.
        WindowsIdentity^ newId = gcnew WindowsIdentity(dupeTokenHandle);
        WindowsImpersonationContext^ impersonatedUser = newId->Impersonate();
        // Check the identity.
        Console::WriteLine("After impersonation: {0}", WindowsIdentity::GetCurrent()->Name);
        // Stop impersonating the user.
        impersonatedUser->Undo();
        // Check the identity.
        Console::WriteLine("After Undo: {0}", WindowsIdentity::GetCurrent()->Name);
        // Free the tokens.
        if (tokenHandle != IntPtr::Zero)
            CloseHandle(tokenHandle);
        if (dupeTokenHandle != IntPtr::Zero) 
            CloseHandle(dupeTokenHandle);
    }
    catch(Exception^ ex)
    {
        Console::WriteLine("Exception occurred. {0}", ex->Message);
    }
Console::ReadLine();
}// end of function
Why should Microsoft's code succeed, where mine fails?