Solved

URG: ADSI remote access problem

Posted on 2004-04-18
19
1,516 Views
Last Modified: 2012-08-13
Hi,
I am using ADSI through c# to create a website in IIS5.0 There are couple of examples on the net and they are pretty much the same. My code works on localhost, however when I am trying to use it specifying a remote machine (thats what I ultimately need to do) I get a COM exception and "Access Denied" message.

DirectoryEntry is used to access the remote machines ADSI. I use administrator's username and password and I have tried many things. Username by itself and with domain as included. I tried all AuthenticationMethod propery values. I also found few pages on the net, but nothign helps to solve the problem. I assume it is to do something with permissions or access rights.

I used WMI with the same username and password no problem (System.Management), however ADSI doen't work. My first question is if it is possible to create IIS5.0 website with WMI. I know it is possible for IIS6.0, but I couldn't find anything on IIS5.0. My other question is what else should I try to resolve my ADSI access problem? What could I try and where should I look? May be some links to info?

Thanks

P.S. Please tell me if you need anything clarifying or a code example.
0
Comment
Question by:basslogik
19 Comments
 
LVL 5

Expert Comment

by:tgannetts
ID: 10858047
Could you show the code you are using?

Tom.
0
 

Author Comment

by:basslogik
ID: 10858935
Ok very simple example. I actually have the whole thing wrapped in the class, but it would be irelevant. I will just show the connection bits.

using System.DirectoryServices;

DirectoryEntry connection;
string serverName = "MyRemoteServer";
string username = domain\\administrator_username";
string password = "AnAdminPassword";

connection = new DirectoryEntry("IIS://" + serverName + "/W3SVC");
connection.Username = username;
connection.Password = password;
connection.AuthenticationType = AuthenticationTypes.Secure;
      
// Test connecton 1
Console.WriteLine(connection.NativeGuid.ToString());
                        
// Test connection 2
foreach (DirectoryEntry e in connection.Children) {e.Close();}

So in the example above I connect to ADSI. I use 2 test methods to test the connection. Thats where the error will occur. When I use servName = "localhost" everything runs fine, just like it should. So I know this is not a code problem. However when serverName is set to a remote Windows 2000 server, I get:
"An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in system.directoryservices.dll
Additional information: Access is denied"

A screenshot is included here: http://tom.drumandbass.ru/stuff/adsi_error.jpg


I don't know where the access could be disallowed. I am pretty new to ADSI, so not even sure where to start looking. I use my administrators account which allows me unrestricted admin control on all servers. I added myself to administrators group on the remote box just in case. Still no luck. I think it is also worth mentioning that I use the code in Windows Service (and currently in a console app, to make testin easier), so all info on the net I found on this error was related to ASP.NET websites which do not have enough permissions.
0
 

Author Comment

by:basslogik
ID: 10859776
I think I am on the track to solve the problem. It seems that I have to impersonate in order to use ADSI, as for some reason it is trying to use the username I am loged on with, and not the username and password I pass to DirectoryEntry contructor (same as setting Username and Password properties). So if you know please provide some help on how to impersonate, as thats what I am trying to search for now. I will post the resolution here if I get there first.

Cheers,
Max
0
 

Author Comment

by:basslogik
ID: 10860855
Right, problem after problem it seems.

so I figured out why I can't connect to the remote machine.

So let me explain. Assume ComputerA is the machine where I have my .NET application, and ComputerB is the remote Windows 2000 server where I am trying to create a website using ADSI.

So if I check the security log on ComputerB (remote) I can see ComputerA trying to log in using username I am logged in to that machine with (e.g. maxim). When I added this username to administrators group on Machine B, the problem dissapeared and I was able to connect and do stuff, no problem. Now, I assumed that I can impersonate to be the user I want that is already in the administrators group (the username I was trying to use say, ad_maxim). I found a briliant wraper for this stuff and I know it works, as I check using "WindowsIdentity.GetCurrent().Name.ToString()".

Now I can see that the identity changes, however I get the Access Denied message still. I checked security logs and it is trying to use my normal username whith which I logged in to ComputerA. This is really confusing as I think that impersonalisation works, as it shows a different account, however it still uses other username. Can anyone help with this? Full points will go for an answer that works, or for any info that helps me to resolve it.

I use something like this:
DirectoryEntry connection;
connection = new DirectoryEntry("IIS://" + inServerName + "/W3SVC");
connection.Username = "username";
connection.Password = "password";
connection.AuthenticationType = AuthenticationTypes.Secure;
                        
Console.WriteLine(WindowsIdentity.GetCurrent().Name.ToString()); // returns normal username

// this is where I call the wrapped. The function is ImpersonateUser
WindowsImpersonationContext imp = Impersonate.ImpersonateUser("domain", "username", "password");
                        
Console.WriteLine(WindowsIdentity.GetCurrent().Name.ToString()); // this shows my admin account, just what i need
                        
Console.WriteLine(connection.NativeGuid.ToString()); // this is to test the access, if no access exception is raised

// do not impersonate anymore
imp.Undo();

Console.WriteLine(WindowsIdentity.GetCurrent().Name.ToString()); // returns my normal username again, just like it should


Any ideas?
0
 
LVL 11

Expert Comment

by:Agarici
ID: 10861219
maybe the connection creates a new thread, runs from that thread  and may be that thread does not inherit the changed credentials

question: if you start your app using the windows runas... does it work ok?

0
 

Author Comment

by:basslogik
ID: 10861674
Yeah I was thining in the same way. May be when I launch DirectoryEntry, the com operation still runs as normal username.

And yes you were right, when I use "runas" and specify the account I need to use, the application seems to work. Any ideas how I could solve the problem? At the moment I am using console app to test the class that connects to IIS, but I am going to use it in windows service, so "runas" will not work there. :S

0
 
LVL 11

Accepted Solution

by:
Agarici earned 100 total points
ID: 10861779
yes, but you can run/start the service under a difrent user than the local service

you can specify things like user and passwd at install time ( see the ServiceProcessInstaller class )
0
 

Author Comment

by:basslogik
ID: 10862300
I see :)

Do you think there is anyway of fixing what I have done above.

If you read help on
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdirectoryservicesauthenticationtypesclasstopic.asp

you can see different authentication methods you can use with DirectoryEntry.
I use secure. Below is direct quote from MSDN.
"Requests secure authentication. When this flag is set, the WinNT provider uses NTLM to authenticate the client. Active Directory uses Kerberos, and possibly NTLM, to authenticate the client. When the user name and password are a null reference (Nothing in Visual Basic), ADSI binds to the object using the security context of the calling thread, which is either the security context of the user account under which the application is running or of the client user account that the calling thread is impersonating."

It sais ASDI binds to the2 object using the security context of the current thread, which I know I changed using impersonation because "WindowsIdentity.GetCurrent().Name.ToString()" returns the right results?!!?
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 

Author Comment

by:basslogik
ID: 10866885
Also I tried configuring the service to run under the different username, but didn't succeed
0
 

Author Comment

by:basslogik
ID: 10867940
Well now I managed to run the service as different username (the problem was that I had to add that user to my local machine's administrators group), I could access remote system using WMI. Still I think this is imperfect solution as it miean I have a service running on priveleded account having access to 6 production servers... I though that impersonafication would be the best way since you can drop priveledges after you are finished with them (Undo() method)... I guess I am stuck with using the username to run the service.

Still, if you know what could be the problem, please post your answer here. The question is still open and points are aviliable. I think the main problem is that setting Username and Password for DirectoryEntry doesn't really work, coz it still tries acessing remote machine using credentials of the currently logged in user, and not someone I am impersonating. This is werid as if you start the process as another user it works.. (e.g. win service, or use runas for console app)... what could be the diference between starting process as some user and impersonating.... :S

M
0
 
LVL 11

Expert Comment

by:Agarici
ID: 10869919
i'm guessing now, but maybe the problem is in your impersonation wrapper
how do you call LogonUser? are you sure so that the logon is valid for network connections and not only locally?

also, (i suppose you did, but just in case) did you try seting the user and password to null so that 'connection' object uses the security context of the calling thread?
0
 

Author Comment

by:basslogik
ID: 10869999
Well you are guessing right. I though of it yesterday and have tried various impersonation types. I am assuming if you use value of 3 it impersonates as NETWORK_ something. Well i tried different methods of connecting but it still seems ot use my logon credentials. As I mentoned before I am running the service as a powerful user now. However it is a workaround and not the best one. Would be much better to impersonate and then drop priveleges after operations are done. Just in case I will paste the code of impersonation wrapper I use. It doesn't include dll imports and other ireelevant stuff

public static WindowsImpersonationContext ImpersonateUser(string sDomain, string sUsername, string sPassword)
{
      // initialize tokens
      IntPtr pExistingTokenHandle = new IntPtr(0);
      IntPtr pDuplicateTokenHandle = new IntPtr(0);
      pExistingTokenHandle = IntPtr.Zero;
      pDuplicateTokenHandle = IntPtr.Zero;
   
      // if domain name was blank, assume local machine
      if (sDomain == "")
            sDomain = System.Environment.MachineName;
      try
      {
      string sResult = null;

      const int LOGON32_PROVIDER_DEFAULT = 0;

      // create token
      const int LOGON32_LOGON_INTERACTIVE = 2;
      //const int SecurityImpersonation = 2;

      // get handle to token
      bool bImpersonated = LogonUser(sUsername, sDomain, sPassword,
            2, LOGON32_PROVIDER_DEFAULT,
            ref pExistingTokenHandle);

      /*bool bImpersonated = LogonUser(sUsername, sDomain, sPassword,
            LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                              ref pExistingTokenHandle);*/

      // did impersonation fail?
      if (false == bImpersonated)
      {
      int nErrorCode = Marshal.GetLastWin32Error();
      sResult = "LogonUser() failed with error code: " + nErrorCode + "\r\n";
      // Error
      lastError = sResult;
      return null;
      }

            // Get identity before impersonation
            sResult += "Before impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n";

            bool bRetVal = DuplicateToken(pExistingTokenHandle,
                  (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
                  ref pDuplicateTokenHandle);

            // did DuplicateToken fail?
            if (false == bRetVal)
            {
                  int nErrorCode = Marshal.GetLastWin32Error();
                  // close existing handle
                  CloseHandle(pExistingTokenHandle);
                  sResult += "DuplicateToken() failed with error code: " + nErrorCode + "\r\n";
                  // Error
                  lastError = sResult;
                  return null;
            }
            else
            {
                  // create new identity using new primary token
                  WindowsIdentity newId = new WindowsIdentity(pDuplicateTokenHandle);
                  WindowsImpersonationContext impersonatedUser = newId.Impersonate();
                  // check the identity after impersonation
                  sResult += "After impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n";
           
                  // Success
                  return impersonatedUser;
            }
      }
      catch (Exception ex)
      {
            throw ex;
      }
      finally
      {
            // close handle(s)
            if (pExistingTokenHandle != IntPtr.Zero)
            CloseHandle(pExistingTokenHandle);
            if (pDuplicateTokenHandle != IntPtr.Zero)
                  CloseHandle(pDuplicateTokenHandle);
            }
      }

sorry for the layout tabbing has gone wrong after pasting. The number 2 (as you can see bImpersonated) is for INTERACTIVE_LOGON, but I have tried other values... CAn you see anything wrong with the code?

Cheers,
Max
0
 

Author Comment

by:basslogik
ID: 10870010
Oh yeah, I have tried using null values, no success. Thats what bothers me so much, and that why I have not given up for 3 days and tried everythign I coud think of. I though that it should be working as it sais in documentation, but it doesn't!
0
 
LVL 11

Expert Comment

by:Agarici
ID: 10875795
try using LOGON32_LOGON_NEW_CREDENTIALS(9)  with null username and pass oin connection
0
 

Author Comment

by:basslogik
ID: 10876160
Nope didn't work, sorry. How strange is all that.
0
 

Author Comment

by:basslogik
ID: 10890055
Thanks
0
 

Expert Comment

by:jan0278
ID: 11824063
Hi,

My question is on the same topic.

 am stuck up with the issue of accessing the IIS 6.0 using the System.DirectoryServices Class of .NET.
 
I am getting an error on line 52 of the code attached with this mail.

foreach(System.DirectoryServices.DirectoryEntry v in IISAdmin.Children)
IISAdmin.Children gives the foll. error.

An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in system.directoryservices.dll
Additional information: Access is denied

Basically i try to access all the children collection of the DirectoryEntry class.
Reference: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdirectoryservicesdirectoryentryclasstopic.asp
This piece of code runs fine with XP and IIS 5.1.
 
Kindly do help me on this.
Thanks !!!

Pranav Shah

___________Code Begins _______________

using System;
using System.DirectoryServices;

namespace IIS
{
      public class IISManager
      {
            /// <summary>
            ///  Constructor
            /// </summary>
 
            /// <summary>
            ///  Default constructor uses localhost as default server
            /// </summary>
            public IISManager()
            {
   
            }
            public string CreateVDir(string  WebSite, string VDirName, string Path, bool  RootDir,bool chkRead,bool chkWrite, bool chkExecute, bool chkScript, bool chkAuth,             int webSiteNum, string serverName)
            {
                  string sRet=String.Empty;
                  System.DirectoryServices.DirectoryEntry IISSchema;
                  System.DirectoryServices.DirectoryEntry IISAdmin;
                  System.DirectoryServices.DirectoryEntry VDir;
                  bool IISUnderNT;

                  //
                  // Determine version of IIS
                  //
                  Console.WriteLine(System.Security.Principal.WindowsIdentity.GetCurrent().Name);
                  IISSchema = new  System.DirectoryServices.DirectoryEntry("IIS://" +serverName + "/Schema/AppIsolated");
                  if  (IISSchema.Properties["Syntax"].Value.ToString().ToUpper() == "BOOLEAN")
                        IISUnderNT = true;
                  else
                        IISUnderNT = false;
                  IISSchema.Dispose();

                  //
                  // Get the admin object
                  //
                  //IISAdmin = new  System.DirectoryServices.DirectoryEntry("IIS://" +serverName +  "/W3SVC/" + webSiteNum + "/Root");
                  IISAdmin = new  DirectoryEntry("IIS://" +serverName +  "/W3SVC/1/Root");
                  IISAdmin.Username = "cmc\ruttarav";
                  IISAdmin.Password = "m0rph0ues";
                  IISAdmin.AuthenticationType = AuthenticationTypes.Secure;
                  
                  Console.WriteLine("After Directory Entry");
                  //
                  // If we're not creating a root directory
                  //
                  if (!RootDir)
                  {
                        //
                        // If the virtual directory already exists then delete it
                        //
   
                        foreach(System.DirectoryServices.DirectoryEntry v in IISAdmin.Children)
                        {
                              if (v.Name == VDirName)
                              {
                                    // Delete the specified virtual directory if it already exists
                                    try
                                    {
                                          IISAdmin.Invoke("Delete", new string [] { v.SchemaClassName, VDirName });
                                          IISAdmin.CommitChanges();
                                    }
                                    catch(Exception ex)
                                    {
                                          sRet+=ex.Message;
                                    }
                              }
                              Console.WriteLine(v.Name);
                        }
                  }  

                  //
                  // Create the virtual directory
                  //
                  if (!RootDir)
                  {
                        VDir = IISAdmin.Children.Add(VDirName, "IIsWebVirtualDir");
                  }
                  else
                  {
                        VDir = IISAdmin;
                  }

                  //
                  // Setup the VDir
                  //
                  VDir.Properties["AccessRead"][0] = chkRead;
                  VDir.Properties["AccessExecute"][0] = chkExecute;
                  VDir.Properties["AccessWrite"][0] = chkWrite;
                  VDir.Properties["AccessScript"][0] = chkScript;
                  VDir.Properties["AuthNTLM"][0] = chkAuth;
                  VDir.Properties["EnableDefaultDoc"][0] = true;
                  VDir.Properties["EnableDirBrowsing"][0] = false;
                  VDir.Properties["DefaultDoc"][0] = true;
                  VDir.Properties["Path"][0] = Path;

                  //
                  // NT doesn't support this property
                  //
                  if (!IISUnderNT)
                  {
                        VDir.Properties["AspEnableParentPaths"][0] = true;
                  }

                  //
                  // Set the changes  
                  //
                  VDir.CommitChanges();

                  //
                  // Make it a web application
                  //
                  if (IISUnderNT)
                  {
                        VDir.Invoke("AppCreate", false);
                  }
                  else
                  {
                        VDir.Invoke("AppCreate", 1);
                  }

                  sRet+= "VRoot " +VDirName + " created!";
                  return sRet;
            }

 
            #region Properties

            public string ServerName
            {
                  get
                  {
                        return _serverName;
                  }
                  set
                  {
                        _serverName = value;
                  }
            }
            #endregion
 
            public static string VirDirSchemaName = "IIsWebVirtualDir";
 
            #region Private Members
            private string _serverName;
 
            #endregion
      }



      class Class1
      {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main(string[] args)
            {
                  //                  VirtualDirectory.IISManager i = new VirtualDirectory.IISManager("xp-pas-pshah");
                  //                  i.Connect();
                  //                  i.CreateVirtualDirectory("Kuku","C:\\Pranav");

                  IISManager i = new IISManager();
                  i.CreateVDir("localhost","Kuku","C:\\Pranav",false,true,true,true,true,false,1,"xp-pas-pshah");

            }
      }
}
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Summary: Persistence is the capability of an application to store the state of objects and recover it when necessary. This article compares the two common types of serialization in aspects of data access, readability, and runtime cost. A ready-to…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
This tutorial demonstrates a quick way of adding group price to multiple Magento products.

757 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now