views:

362

answers:

2

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?

+1  A: 

"Why should Microsoft's code succeed, where mine fails?"

Because your code does something different :-)

In this line:

bool returnValue = LogonUser(userName, domainName, Console::ReadLine(), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
    &tokenHandle);

note that 'tokenHandle' is passed by reference, but in your version:

bSuccess = LogonUser(_srUsername, _srDomain, _srPassword, 8, 0, (IntPtr*)token.ToPointer());

you are not passing 'token' by reference, so your local reference will not be updated, which is why it is still zero when you pass it to the WindowsIdentity.

Jim Arnold
That is what I had thought before too, which is why its that way in my current code, but sadly this also fails. I've changed the code above so that it reflects that change.
I don't see how your edited code would work either - you're now passing tokenHandle by ref, but you're not using it anywhere else!
Jim Arnold
A: 

I think that the answer is in MSDN article describing the LogonUser function:


The LOGON32_LOGON_NETWORK logon type is fastest, but it has the following limitations:

The function returns an impersonation token, not a primary token. You cannot use this token directly in the CreateProcessAsUser function. However, you can call the DuplicateTokenEx function to convert the token to a primary token, and then use it in CreateProcessAsUser.


Andrey