Download source files - 40 Kb
In one of my projects I needed a delegation feature for COM. The server was
running under a high-privileged account and needed to issue COM calls on behalf of
low-privileged clients to another COM server. After searching in MSDN I found that
the current situation is pretty simple: "COM does not support delegation".
Although named pipes in the Windows NT environment support it for single machine
operations - COM does not. To overcome this situation I created my own user impersonation
mechanism based on the usage of the COM channel hook interface.
The idea behind the COM channel hook mechanism is described in the very good
and comprehensive way in January'98 issue of MSJ. In short, an application could register a COM
callback interface that will be called by the COM engine when it sends or receives a call. So
The application can add whatever information it needs in a way that is transparent to the user.
The callback interface IChannelHook
consists of the following functions:
void ClientGetSize(REFGUID uExtent,
REFIID riid,
ULONG *pDataSize);
void ClientFillBuffer(REFGUID uExtent,
REFIID riid,
ULONG *pDataSize,
void *pDataBuffer);
void ClientNotify(REFGUID uExtent,
REFIID riid,
ULONG cbDataSize,
void *pDataBuffer,
DWORD lDataRep,
HRESULT hrFault);
void ServerNotify(REFGUID uExtent,
REFIID riid,
ULONG cbDataSize,
void *pDataBuffer,
DWORD lDataRep);
void ServerGetSize(REFGUID uExtent,
REFIID riid,
HRESULT hrFault,
ULONG *pDataSize);
void ServerFillBuffer(REFGUID uExtent,
REFIID riid,
ULONG *pDataSize,
void *pDataBuffer,
HRESULT hrFault);
When a COM client issues call to server, first ClientGetSize
is called on the client's
side to determine size of additional data and then ClientFillBuffer
to fill buffer with
actual data. When COM call arrives to the server - server side gets notified about arrived
data through ServerNotify
function. Functions ServerGetSize
and ServerFillBuffer
are
called on the way back on the server's side and, finally, ClientNotify
when the call
returns on the client's side.
To make delegation possible I attach the following information to the COM hook buffer:
- The process ID of the process that issued a call
- The network name of the computer that issued a call
- The user token of the thread that issued a call (if it exists) or the process-wide user token
(if it does not)
When the data arrives on the server's side the code first checks whether the client and
server reside on the same machine using the transferred machine name. This is because the
user token that we have transferred makes sense only on the same machine. If the machine is the same,
the server tries to open a process that issued a call and duplicate the token for it to be valid
for the server's process. Then it uses the duplicated token to call
ImpersonateLoggedOnUser
function to do actual user impersonation.
All information about incoming calls is kept in the thread local storage (TLS) on a
per-thread basis. To avoid losing information in case of recursive COM calls, the
information about incoming calls is kept as a stack and every new call pushes its new
context on the stack and removes it on the way back to the server.
The COM channel hook will be installed automatically as long as DLL is loaded into memory
of the process. This could be done either by an explicit call to the LoadLibrary
function or implicitly by a CoCreateInstance
call creating IClientInfo
interface.
For the proper functionality of the discussed user impersonation method the channel hook
must be installed both in the client and server processes. For my project I managed to do
it automatically for the client and for the server by initializing the interface to the IClientInfo
from the Proxy-Stub DLL that is automatically generated by MSVC Wizard. To do so you need
to create a new "c" file that looks like this :
#include <windows.h>
#include "..\COMchannelinfo\dcom_channel_info.h"
#include "..\dcomchannelinfo\dcom_channel_info_i.c"
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
BOOL WINAPI newDllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HRESULT hr;
static IClientInfo* client_info_iface;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
client_info_iface = NULL;
hr = CoCreateInstance(&CLSID_ClientInfo, NULL,
CLSCTX_INPROC_SERVER, &IID_IClientInfo,
(void**)&client_info_iface);
if (FAILED(hr))
{
return FALSE;
}
break;
case DLL_PROCESS_DETACH:
__try
{
if (client_info_iface != NULL)
{
client_info_iface->lpVtbl->Release(client_info_iface);
}
} __except (1)
{
};
break;
}
return DllMain(hinstDLL, fdwReason, lpvReserved);
}
And then patch a generated make-file for your proxy-stub DLL (usually it has the name of the
project + "ps.mk" at the end) and make the following changes to it:
rcserverps.dll: dlldata.obj rcserver_p.obj rcserver_i.obj rcserverps_dllmain.obj (this is a new object file)
link /dll /out:rcserverps.dll /def:rcserverps.def /entry:newDllMain \ (this is new DLL entry point)
dlldata.obj rcserver_p.obj rcserver_i.obj rcserverps_dllmain.obj \(this is a new object file)
kernel32.lib rpcndr.lib rpcns4.lib rpcrt4.lib oleaut32.lib uuid.lib ole32.lib (additional standard library)
The effect of this operation is the following: as soon as you make a first call to your
COM interface the proxy-stub DLL will be loaded automatically by the COM engine. In its
DLLMain
it loads and installs our COM channel hook that transfers the
required information about the called user automatically. You have an advanced impersonation
routine for free! :)
Using the interface in applications.
You need to register the DLL with the call to regsvr32.exe dcom_channel_info.dll.
Source, Demos, updates and legal stuff.
The latest source and updates for the application can be found here.
The Interface itself is used in the Remote Control application (in its server part) which
can be used as an example on usage of this impersonation method. It also could be found here.
You can drop an e-mail to author here.
Big thanks to Don Box for pointing out an elegant
solution to my problem.