views:

135

answers:

1

I have an old Win32 C++ DCOM Server that I am rewriting to use C# .NET 3.5. The client applications sit on remote Windows XP machines and are also written in C++. These clients must remain unchanged, hence I must implement the interfaces on new .NET objects.

This has been done, and is working successfully regarding the implementation of the interfaces, and all of the calls are correctly being made from the old clients to the new .NET objects.

However, I'm having problems obtaining the identity of the calling user from the DCOM Client. In order to try to identify the user who instigated the DCOM call, I have the following code on the server...

[DllImport("ole32.dll")]
static extern int CoImpersonateClient();

[DllImport("ole32.dll")]
static extern int CoRevertToSelf();

private string CallingUser
{
    get
    {
        string sCallingUser = null;

        if (CoImpersonateClient() == 0)
        {
            WindowsPrincipal wp = System.Threading.Thread.CurrentPrincipal as WindowsPrincipal;

            if (wp != null)
            {
                WindowsIdentity wi = wp.Identity as WindowsIdentity;

                if (wi != null && !string.IsNullOrEmpty(wi.Name))
                    sCallingUser = wi.Name;
            }

            if (CoRevertToSelf() != 0)
                ReportWin32Error("CoRevertToSelf");
        }
        else
            ReportWin32Error("CoImpersonateClient");

        return sCallingUser;
    }
}

private static void ReportWin32Error(string sFailingCall)
{
    Win32Exception ex = new Win32Exception();
    Logger.Write("Call to " + sFailingCall + " FAILED: " + ex.Message);
}

When I get the CallingUser property, the value returned the first few times is correct and the correct user name is identified, however, after 3 or 4 different users have successfully made calls (and it varies, so I can't be more specific), further users seem to be identified as users who had made earlier calls.

What I have noticed is that the first few users have their DCOM calls handled on their own thread (that is, all calls from a particular client are handled by a single unique thread), and then subsequent users are being handled by the same threads as the earlier users, and after the call to CoImpersonateClient(), the CurrentPrincipal matches that of the initial user of that thread.

To illustrate:

User Tom makes DCOM calls which are handled by thread 1 (CurrentPrincipal correctly identifies Tom)

User Dick makes DCOM calls which are handled by thread 2 (CurrentPrincipal correctly identifies Dick)

User Harry makes DCOM calls which are handled by thread 3 (CurrentPrincipal correctly identifies Harry)

User Bob makes DCOM calls which are handled by thread 3 (CurrentPrincipal incorrectly identifies him as Harry)

As you can see in this illustration, calls from clients Harry and Bob are being handled on thread 3, and the server is identifying the calling client as Harry.

Is there something that I am doing wrong? Are there any caveats or restrictions on using Impersonations in this way? Is there a better or different way that I can RELIABLY achieve what I am trying to do?

All help would be greatly appreciated.

A: 

OK, so I've taken a different appropach, and finall come up with a method that seems to work (tested for 8 different remote users).

I've ditched the Impersonation route in favour of ClientBlankets...

[DllImport("ole32.dll")]
static extern int CoQueryClientBlanket(out IntPtr pAuthnSvc, out IntPtr pAuthzSvc,
    [MarshalAs(UnmanagedType.LPWStr)] out StringBuilder pServerPrincName, out IntPtr
    pAuthnLevel, out IntPtr pImpLevel, out IntPtr pPrivs, out IntPtr pCapabilities);

public static string CallingUser
{
    get
    {
        IntPtr pAthnSvc = new IntPtr();
        IntPtr pAthzSvc = new IntPtr();
        StringBuilder pServerPrincName = new StringBuilder();
        IntPtr pAuthnLevel = new IntPtr();
        IntPtr pImpLevel = new IntPtr();
        IntPtr pPrivs = new IntPtr();
        IntPtr pCaps = new IntPtr(4);
        string sCallingUser = string.Empty;

        try
        {
            CoQueryClientBlanket(out pAthnSvc,
                out pAthzSvc,
                out pServerPrincName,
                out pAuthnLevel,
                out pImpLevel,
                out pPrivs,
                out pCaps);
        }
        catch (Exception ex)
        {
            Logger.Write(ex.Message);
        }
        finally
        {
            sCallingUser = System.Runtime.InteropServices.Marshal.PtrToStringAuto(pPrivs);
        }

        return sCallingUser;
    }
}

Using CoCreateClientBlanket seems to have the desired results, and I'm able to reliably obtain the identity of the calling user everytime, regardless of which thread is used to process the message.

pionium