Last post Sep 04, 2006 06:07 PM by dunnry
Aug 25, 2006 03:42 AM|bgriley|LINK
I worked as part of a .NET development team a few years ago and our 1.0 vb.NET web application was deployed and running fine (minor glitches) on a Windows 2000 Advanced Server running IIS5. Application is configured to do Active Directory authentication.
I left team over two years ago and now company has called me back because I'm the only former developer still in the area. I've been out of the .NET loop since then and am basically over my head on this one. However, company wants problem fixed yesterday!
Their problem is that their client who uses application asked to have it upgraded to .NET 1.1 so it could be deployed on a Windows 2003 Server running IIS 6. Someone at company basically recompiled application using Visual Studio 2003/.NET 1.1 and shipped
back to clietn for testing. My understanding is that there were minimal changes made to the code. Now that its deployed in client's test environment, the application gets to a point 2 - 4 times a day where they're getting Active Directory signon failures
. Once they start getting errors, they don't stop until an IISRESET is issued. Event log has entries such as:
Event Type: Error
Event Source: [REMOVED BY AUTHOR]
Event Category: None
Event ID: 0
Time: 4:36:06 PM
Computer: [REMOVED BY AUTHOR]
System.Runtime.InteropServices.COMException (0x8007203A): The server is not operational
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at WBSecurity.wbAuth.GetUserPath(String pstrUsername) in C:\Documents and Settings\[REMOVED BY AUTHOR]\My Documents\Visual Studio Projects\Workbench01\TBO\WBSecurity\wbAuth.vb:line 689
I've found numerous posts discussing this topic. I've reworked the AD code to ensure that DirectoryEntry and DirectorySearcher objects are being disposed in Finally block. Application with this code change was sent to client, but they are still getting
same errors. In monitoring w3wp process when application is being accessed from client, the memory usage will go up, but doesn't seem drop back down. At some point, the Active Directory singon failures start occurring. I'm curious as to whether there is
some flaw in the login or other pages that is keeping memory from being released.
I attached Debug Diagnostics 1.0 to w3wp process and created a manual dump and then ran an analysis. There is much information in report, but I am not able to decipher it.
Again, I am not a .NET guru, but was just brought in because I was the only past developer who could be found (or perhaps allowed himself to be found ;). I can post some of the key portions of code if that would help. Not sure if I can attach Debug Diagnostics
report (its over 1MB, zipped, 146KB). Like I said client wants it yesterday and I'm trying my best to resolve
Any help would be appreciated.
Aug 25, 2006 04:59 PM|dunnry|LINK
Aug 25, 2006 08:19 PM|bgriley|LINK
Aug 26, 2006 01:47 AM|bgriley|LINK
Fake values substituted.
Connection string from web.config key:
value="LDAP://CN=appAuthUsers,OU=appAuthentication,OU=COMPANY Local Groups,DC=office1,DC=company,DC=org"
Yes, serverless bind because we need redundancy.
DirectorySearcher.FindAll() is done using DE found for above authenticating path and SAM Account Name.
Other access to AD occurs to determine all groups user is a member of and all groups that have access to application. These two lists are cross referenced to see if user has access to application. Also,
once user is authenticated, another hit against AD occurs to figure out all the report groups that user belongs to. These might be pretty memory intensive.
Yes, IIS server is in same domain, with caveat. All current test users are in same domain. Most future users will be in same domain, but some will be in different domain. Regardless, the problem is
occuring right now with just users from the same domain accessing application.
AD Admin user and password maintained in config file.
Web application is running in its own application pool under Network Service.
Anonymous access off. Impersonate=true in config file.
When I monitor memory usage it goes progressively up without dropping back down after user is logged in and off page that fired AD logic. Not sure if this is expected behavior or not. Perhaps GC hasn't
occurred to reclaim memory.
I can recreate the problem. I did so last night and generated a manual dump and resultant analysis report in Debug Diagnostic. I'm not sure why it didn't create dump automatically. I can send if I'm able to post files here. Its 164kb zipped. I can also
post code if that would help.
Not sure if your monitoring on weekends, but I'll be on problem from about noon tomorrow until end of weekend (with some sleep in between of course).
Thanks again for your help,
Aug 28, 2006 11:12 PM|dunnry|LINK
So, serverless binding is a fine choice -however it comes with a caveat to making it work. The unmanaged process needs to be running in a domain context, or the serverless binding won't work. Generally, we do this by running the application in an App Pool
running under a domain identity, or by impersonating a domain account during the request. When you see the server not operational error, it generally means you have tried serverless binding from a local account.
Other observations I can make (without seeing source, not a whole lot really) - I would guess that your code is not calling Dispose on all (and I mean all) DirectoryEntry objects as well as any hidden ones created by the DirectorySearcher. Additionally,
you can be exposed to a memory leak in v1.1 if you are using FindOne() anywhere and you do not find any results. There are simple workarounds for the FindOne() leak and you just need to call Dispose on all DirectoryEntry and SearchResultCollection objects.
The Try/Finally pattern for VB.NET or the 'using' pattern for C# work well here.
What I am unsure of here is that you have impersonation enabled, but it sounds like to me you are passing explicit credentials (via the config file's admin credentials). Is this the case? Or is it both?
So, depending on your scenario, I would really recommend to revisit your code to make sure you have called Dispose on everything (even New ones or Children DirectoryEntry objects). Next, I would turn off impersonation if you are not using delegation (doesn't
sound like you are). Finally, I would just put a domain service account as the app pool identity and remove any explicity credentials from your DirectoryEntry objects so they access AD with the service account identity. This is the easiest to use and gives
you some nice deployment options.
Sep 01, 2006 01:35 AM|bgriley|LINK
Thanks for the reply. Prior to this thread, I had already pored through the code and made changes to ensure that all DirectryEntry and DirectorySearcher objects were disposed in Finally blocks. Still didn't help us. We ended up calling MS PSS team and
it looks like our issue has been resolved. Issue hasn't occurred since Monday config changes. After inquiring as to whether all the domains involved are two way trusts, it seems the major fix was to add the domain name to the search string used to create
the DirectoryEntry objects. So the key for authenticating path was changed so domain name followed by a slash was added after the LDAP:// string something like the following:
value="LDAP://office1.company.org/CN=appAuthUsers,OU=appAuthentication,OU=COMPANY Local Groups,DC=office1,DC=company,DC=org"
This seems to have resolved our problem.
They also suggested some code changes as you did to ensure disposal of DirectoryEntry and DirectorySearcher objects. On top of that they had some other suggestions: 1. Issue a Directory.Close() before disposal, 2. Dispose all SearchResultCollection objects,
3. Add just the properties we need on DirectorySearcher objects using PropertiesToLoad.Add method and 4. In all user search strings, add (objectCategory==user) before (objectClass=user).
I haven't had time to get to all the suggested changes, but I will.
Sep 04, 2006 06:07 PM|dunnry|LINK
Everything I said earlier still stands... the reason you need to add the domain to the string is because your app is running under an account that cannot resolve the serverless bind. You had told me that everything was in the same domain or I would have
suggested this technique from outside the domain. It is not necessary in same domain if your security context is setup correctly.
Now... in order of their advice:
1. Rubbish. They don't know what they are talking about here... Close() is almost completely worthless. The only difference between Close() and Dispose() is that the DirectoryEntry is marked as disposed in the latter case and cannot be reused...
2. I said that earlier as well...
3. This is a search optimization and I would recommend this as well but it has no bearing on your current issue.
4. This is a search optimization to use an indexed attribute... again that is great but no bearing on your issue.