Link to home
Start Free TrialLog in
Avatar of andrewjackson
andrewjackson

asked on

DCOM - security headache

I've a D4 DCOM server installed on NT 4 (SP5).  D4 client is installed on another PC, also running NT4 (SP5).

When logged onto the client and server PCs with same NT user account then client can launch server without any problems.  However, if logged onto client and server with different NT user accounts then client cannot launch server.  In this case Client can only connect to server if it is already running.

I've tried various DCOMCNFG settings but to no avail. For the server, I've got user-defined settings for the server object as follows:

Authentication level - connect
Location - run application on this computer
Security - user defined with everyone, interactive & system assigned to access and launch permissions
Identity - interactive user

Since only Client.exe and Server.tlb are installed on client PC the server object doesn't appear in dcomcnfg.  But I have set default DCOM settings on client PC as follows:

Authentication - Connect
Impersonation - Identity
Security - everyone, interactive & system assigned to access and laucnh permissions

I want any client running under any BT account to be able to launch server.  Any ideas where I'm going wrong?
Avatar of KE
KE

Initialize the Security yourself, and bypass the DCOM cfg. utility.

These routines initialize a null security in either STA or MTA mode.
You HAVE to call these before calling any DCOM related object - so place them in your project source before initalizing any forms etc.
If you need a single threaded apartment use STA version, the other one is offcourse for multi threaded apartments.

procedure COM_InitializeNullSecuritySTA;
var
  x : DWORD;
begin
  CoInitializeEx( nil, COINIT_APARTMENTTHREADED );
  x := CoInitializeSecurity( Nil, //Points to security descriptor
                        -1, //Count of entries in asAuthSvc
                        Nil, //Array of names to register
                        Nil, //Reserved for future use
                        RPC_C_AUTHN_LEVEL_NONE, //The default authentication level for proxies
                        RPC_C_IMP_LEVEL_IDENTIFY, //The default impersonation level for proxies
                        Nil, //Reserved; must be set to NULL
                        EOAC_NONE, //Additional client or server-side capabilities
                        Nil //Reserved for future use
                       );
  if x <> 0 then RaiseLastWin32Error;
end;

procedure COM_InitializeNullSecurityMTA;
var
  x : DWORD;
begin
  CoInitializeEx( nil, COINIT_MULTITHREADED );
  x := CoInitializeSecurity( Nil, //Points to security descriptor
                        -1, //Count of entries in asAuthSvc
                        Nil, //Array of names to register
                        Nil, //Reserved for future use
                        RPC_C_AUTHN_LEVEL_NONE, //The default authentication level for proxies
                        RPC_C_IMP_LEVEL_IDENTIFY, //The default impersonation level for proxies
                        Nil, //Reserved; must be set to NULL
                        EOAC_NONE, //Additional client or server-side capabilities
                        Nil //Reserved for future use
                       );
  if x <> 0 then RaiseLastWin32Error;
end;


Regards
Avatar of andrewjackson

ASKER

Thanks, I'll try this.  

Does this code need to be placed in both the server and client code?

Also, ideally I'd like to avoid setting security in code since system administrators cannot override programmed DCOM security.  Out of interest then, do you know why using DCOMCNFG doesn't seem to work?

Andrew




Very valuable stuff here. I'm listening...
KE,

Unfortunately the programmatic approach hasn't worked either.  I've tried
CoInitializeEx and CoInitialize before CoInitializeSecurity but still just get 'Access denied' error if server is not already running.  This code is definately initialized first.

I've since made some other adjustments using dcomcnfg...

In the server's 'Custom launch permissions' I've added a specific user account to the list.  Now, when I try to connect a client running on that user account to a server running on a differnt account the server does at least launch.  However, it must not be launching in interactive mode because my event mechanism (using IConnectionPoints) doesn't work - this only seems to work if the server is launched in interactive mode.

Any more ideas?
Well, I thnik we will need to start on a fresh.

First of all, ALWAYS remember to restart your server app. whenever you make changes in DCOM cfg. The changes are not reflected until you launch the application again (security is intialized either manual or automatic as the first thing). Try to figure out what your problem and especially need's are, as excact as possible - and describe it in as few words as possible here afterwards.

When you have done this, it will be easier to figure out what mode you should launch in, and what the issues are.

Citate:
1. "it must not be launching in interactive mode"
2. "this only seems to work if the server is launched in interactive mode"

See - it's a little confusing, what you want and what you have :-)

IConnectionPoints works in ALL modes, it's just a matter of security issues.

Regards
BTW. Yes the code need's definately to be run in both the client and server, when speaking of events !!!

Regards
Okay, here is more background and detail...

MyServer takes the form of a TAutoObject object, instancing type is ciMultiInstance.  When the first client connects, MyServer.exe is launched and an instance of MyServer object is created.  As more clients connect, more instances of the MyServer COM object are created but all within the same MyServer.exe.  Each MyServer COM object delegates its methods and properties to standard delphi objects.  Many of these are singleton objects so although many instances of MyServer get created, they all point to the same internal objects and data.

The server also has a main form.  This allows system administrators to monitor what is going on.  At launch of MyServer.exe it actually appears as an icon in the system tray.  

The only relevance (I think) of the main form, in terms of DCOM security, is that the form only appears if the server is launched in interactive mode (I picked this up from Binh Ly's COM web-site).  I've also found though that my implementation of COM events mechanism only works if the form is showing (i.e if launched in interactive mode).  It may just be coincidental that 'no form' & 'no events' appear to occur at for the same security settings.

Regardless of that though, my client cannot even launch the server (in any mode) unless either:

1. Client and server are running under same NT user account

or

2. The Client is running under a user account that has been added to the Server's 'Custom launch permissions' list through DCOMCNFG.

What I want is to be able to install server on NT4, and connect to server from range of client machines running NT4, Win9x.  Each client machine will be logged on to an NT server domain, although the user accounts may differ.

Hope this helps

Andrew
Watching  :o)
"Okay, here is more background and detail..."
Whew, certainly - very nice...

1. Running the server as interactive user is the only way to show forms - you're right. What you also have to consider is the logoff sequence, as it will kill your server if not guarded in any way.

2. Your events should work in any mode, you may have to look at how they are fired. Make a dummy routine on your COM object that fires the event and see what happens.
When you use connection points, it's very important that both the client and the server has the same level of security, as DCOM will chose the highest level. This means that if your client is initialized with default security (as it will be if you don't do anything) - the server will not be able to make a connection back to the client - unless that you lower the default security on the client machine. To overcome this, initialize it programatically (as null).

Try this:

A. As for your server, allow Everyone to launch the server (Custom Launch Permissions). Initialize it (as first point in your project source code) with the routines I've provided (below).

B. As for your client, initialize it with the STA routine only.

It should now be working...

Regards



A
----------------------------------
begin
  COM_SetCLSIDAPPID( CLASS_yourautoobjectclass );
  COM_SetRunAs( CLASS_FMClientControl, 'Interactive User' );
  COM_SetEndpoints( CLASS_FMClientControl, ncacn_ip_tcp );
  COM_InitializeNullSecurityMTA;
.....
end.


Here's the helpers:
procedure COM_SetCLSIDAppID( GUID: TGUID );
var
  r : TRegistry2;
begin
  r := TRegistry2.Create;
  try
    r.RootKey := HKEY_CLASSES_ROOT;
    r.OpenKey( '\CLSID\'+GUIDToString(GUID), True );
    r.WriteString('AppID', GUIDToString(GUID) );
  finally
    r.Free;
  end;
end;

procedure COM_SetRunAs( GUID: TGUID; Value: String );
var
  r : TRegistry2;
begin
  r := TRegistry2.Create;
  try
    r.RootKey := HKEY_LOCAL_MACHINE;
    r.OpenKey( '\SOFTWARE\Classes\AppID\'+GUIDToString(GUID), True );
    r.WriteString('RunAs', Value );
  finally
    r.Free;
  end;
end;

procedure COM_SetEndpoints( GUID: TGUID; Value: String );
var
  r : TRegistry2;
  sl: TStringList;
begin
  r := TRegistry2.Create;
  try
    r.RootKey := HKEY_LOCAL_MACHINE;
    r.OpenKey( '\SOFTWARE\Classes\AppID\'+GUIDToString(GUID), True );
    sl := TStringList.Create;
    try
      sl.Text := Value;
      r.WriteMultiString('Endpoints', sl );
    finally
      sl.Free;
    end;
  finally
    r.Free;
  end;
end;
ASKER CERTIFIED SOLUTION
Avatar of KE
KE

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Yep, this code did the trick. Brilliant! The Client can now launch server irrespective of DCOM settings on client or server machines.

In the server I use:
COM_SetCLSIDAPPID
COM_InitializeNullSecurityMTA

In the client I use: COM_InitializeNullSecuritySTA

I did spot a couple of things with your code snippets though...

1. COM_InitializeNullSecurityMTA
My server's main form contains a rich edit control and another form contains an ActiveX control (for serial I/O).  At first calling COM_InitializeNullSecurityMTA caused havoc when the D4 server tried to instantiate these controls (OLE and window handle errors reported).  However, I eventually resolved this by calling CoUninitialize after the call to CoInitializeSecurity.  It hasn't seemed to have affected anything.

2. COM_SetEndpoints
Can't compile, WriteMultiString not defined in TRegistry - I've had to replace your TRegistry2 with TRegistry which obviously doesn't support this method.  What is TRegistry2?

Thanks very much for your help. Much appreciated.

Extrenly heplful response
Great it's working...

1. What if you use the COM_InitializeNullSecuritySTA - maybee the ActiveX control don't like a multi-threaded apartment ?
If you initialize with MTA, your default apartment will be multi-threaded, if you intialize with STA it's single-threaded.

2. Ohh yes - TRegistry2 is a modified registry (D4) that supports multi strings. I can mail it if you like...

Regards
Please mail me the TRegistry2.

Thanks
> ajackson@ccc-group.com !!
Ken,

I've now installed the same server code on 2 PCs and am getting problems again.  I'm getting an 'Interface not supported' error when I try to connect to one of these servers from a remote client, the other server is fine.

Each PC is running NT4 Workstation SP5, and I'm logged on to each PC using the same NT account.  This account has local adminstrative rights on both PCs.

DCOM security is being bypassed in the server since each server calls:
COM_SetCLSIDAPPID(..);
COM_SetRunAs(..,'Interactive User');
COM_InitializeNullSecurityMTA;

Security is being passed in the client since it calls:
COM_InitializeNullSecuritySTA

Even so I've checked DCOMCNFG on both server PC's - default and application-specific settings are exactly the same.

I can connect to one of the servers irrespective of which user account the client is running under.  

However, I can only connect to the other server if the client is running under the same account as the server.  Attempting to connect under any other account generates a 'Inteface not supported' error during the call to coMyServer.CreateRemote(..).  This error appears even if the server is already running.

Any ideas?  I'll start a new thread with additional points if you wish

Regards,

Andrew


I seemed to have solved this one myself but for anyone still interested...

I found I wasn't making the calls to override DCOM security early enough.  I had placed them just before *Application.Initialize* in the project source, not realising that *initialization* sections actually get triggered BEFORE this call.  So security permissions were already established before my code executed. I've now moved the code to a proper *initialization* section.

(Note: You would think my error would cause the DCOMCNFG settings to be used instead.  However, DCOMCNFG settings on both servers were identical yet only one of them could be accessed - I've not worked that one out yet!)

I've did spot another couple of interesting things...

1. I can't seem to call COM_SetRunAs soon enough - even the *initialization* section appears to be too late.  I have to manually the set Identity attribute to *Interactive User* in DCOMCNFG otherwise server fails to load (CreateRemote fails before COM_SetRunAs gets a chance to execute).

2.  The version of DCOMCNFG that ships with NT4 SP3-SP5 contains a bug which seems to cause custom access permissions to be overriden by the default permissions.  This is apparently fixed in NT4 SP6 but I've not tested it.

Regards

Andrew
Hi Andrew,

"The interface not supported" error is because the type-library is not registered on the client machine. To do this find the utility "tregsvr.exe" in you delphi/bin folder. Find your type library (the binary version with a .tlb extension) and run it through tregsvr on the client machine.

Regarding initialization - yes, security has to be set up before you attempt to use any DCOM related stuff (I don't remember exactly what is allowed at the initial state). If you don't time it correctly, DCOM will establish a default security setting based on DCOMCNFG.
Normally this is not a problem with the example I've provided (it works with my server ;-), so unless you do some "home" made unit intializations besides the "automated" ones (AutoObjectFactories) - it should be working.

1. Try to take a look again on your unit initializations. Remove everything that is related to COM (also indirectly) into a procedure and call this(these) before Application.Initialize. Well, you should leave the instantiation of the objectfactories as they was in their proper units.

2. I've not experienced any problems with SP5 and neither SP3. I haven't tryed SP4 and I wait a moment with SP6.
Most of the roumours that I've stumbled accross, said this bug to be associated with SP4 only.

Regards