FTP and LDAP - Part 2: How to Set Up an Active Directory Lightweight Directory Services (AD LDS) Server

This blog is designed as a complement to my FTP and LDAP - Part 1: How to Use Managed Code (C#) to Create an FTP Authentication Provider that uses an LDAP Server blog post. In this second blog, I'll walk you through the steps to set up an Active Directory Lightweight Directory Services (AD LDS) server, which you can use with the custom FTP LDAP Authentication provider that I discussed in my last blog.

In This Blog

Step 1: Installing AD LDS

The following steps will walk you through installing Active Directory Lightweight Directory Services on a computer that is running Windows Server 2008.

Adding the AD LDS Role

  1. Open the Windows Server 2008 Server Manager, click Roles in the navigation pane, and then click Add Roles.
  2. Check the box for Active Directory Lightweight Directory Services, and then click Next.
  3. Read the information on the Introduction to Active Directory Lightweight Directory Services page, and then click Next.
  4. Verify the Confirmation Installation Settings, and then click Next.
  5. The installation will start; this may take several minutes to complete.
  6. When the installation has completed, click Close.

Creating an AD LDS instance

Note: Before completing these steps I created a local user account named "LdapAdmin" that I would specify the administrative account for managing my LDAP instance. This user account was only a member of the local "Users" group, and not a member of the local "Administrators" group.

  1. Click Start, then click Administrative Tools, and then click Active Directory Lightweight Directory Services Setup Wizard.
  2. When the Active Directory Lightweight Directory Services Setup Wizard appears, click Next.
  3. Select A unique instance, and then click Next.
  4. Enter a name for your instance, for example "MyTestInstance," and then click Next.
  5. Verify the port numbers for LDAP connections, and then click Next.
  6. Choose Yes, create an application directory partition, and then enter a unique partition name by using X.500 path syntax. For example: "CN=MyServer,DC=MyDomain,DC=local". When you have finished entering your partition name, click Next.
  7. Verify the paths to the AD LDS files for this instance, and then click Next.
  8. Choose an account for your service account. (Note: Because I was creating a standalone LDAP server, I chose to use the network service account.) Once you have chosen an account, click Next.
  9. If you choose to use the network service account, the AD LDS wizard will prompt you about replication. Click Yes to continue.
  10. Choose an account as your AD LDS administrator. (Note: In my situation I chose the LdapAdmin account that I had created earlier; I did this so that I wouldn't be storing the credentials for an administrative account.) Once you have chosen an account, click Next.
  11. Choose one of the following LDIF files to import; these will be used to create user accounts.
    • MS-User.LDF
    • MS-InetOrgPerson.LDF
    Note: I tested my FTP LDAP authentication provider with both LDIF files.
  12. Verify your installation options, and then click Next.
  13. When prompted for your AD LDS credentials, enter the credentials for the account that you chose to administer your AD LDS instance.
  14. The wizard will begin to install the requisite files and create your instance; this may take several minutes to complete.

  15. When the wizard has completed, click Finish.

Step 2: Using ADSI Edit to add Users and Groups

Connecting to your AD LDS Server

  1. Click Start, and then Administrative Tools, and then ADSI Edit.
  2. Click Action, and then click Connect to...
  3. When the Connection Settingsdialog box is displayed:
    • Enter the LDAP path for your AD LDS server in the Select or type a Distinguished Name or Naming Contexttext box. For example:
      • CN=MyServer,DC=MyDomain,DC=local
    • Enter the server name and port in the Select or type a domain or servertext box. For example:
      • MYSERVER:389
    • The preceding steps should create the following path in the Pathtext box:
      • LDAP://MYSERVER:389/CN=MyServer,DC=MyDomain,DC=local
       
    • Click the Advanced button; when the Advanceddialog box is displayed:
      • Check the Specify Credentials box.
      • Enter the user name and password for your AD LDS server.
      • Click OK.
  4. Click OK.

Adding a User Object

  1. Expand the tree until you have highlighted the correct LDAP path for your server. For example:
    • CN=MyServer,DC=MyDomain,DC=local.
  2. Click Action, and then New, and then Object...
  3. Highlight the appropriate user class, and then click Next.
  4. Enter the common name for your user, and then click Next. For example: enter FtpUser for the common name.
  5. Click Finish.
  6. Right-click the user that you created, and then click Properties.
  7. Select msDS-UserAccountDisabled in the list of attributes, and then click Edit.
  8. Select False, and then click OK.
  9. Select userPrincipalName in the list of attributes, and then click Edit.
  10. Enter your user's common name for the value, and then click OK. For example: enter FtpUser for the common name.
  11. Click OK to close the user properties dialog box.
  12. Right-click the user that you created, and then click Reset Password...
  13. Enter and confirm the password for your user.
  14. Click OK.

Adding Users to Groups

  1. Retrieve the Distinguished Name (DN) for a user:
    • Right-click the user that you created, and then click Properties.
    • Select distinguishedName in the list of attributes, and then click View.
    • Copy the value, and then click OK. For example: CN=FtpUser,CN=MyServer,DC=MyDomain,DC=local.
    • Click OK to close the user's properties dialog box.
  2. Add the user to a group:
    • Expand the tree until you have highlighted a group in your server. For example, you could use the built-in CN-Users group.
    • Right-click the group, and then click Properties.
    • Select member in the list of attributes, and then click Edit.
    • When the editor dialog box is displayed, click Add DN...
    • When the Add Distinguished Name (DN) dialog box appears, paste the user DN syntax that you copied earlier. For example: CN=FtpUser,CN=MyServer,DC=MyDomain,DC=local.
    • Click OK to close the Add DN dialog box.
    • Click OK to close the group's properties dialog box.

More Information

For additional information about working with AD LDS instances, see the following URLs:

Enabling the Custom FTP LDAP Authentication Provider for an FTP site

While this is technically outside the scope of setting up the LDAP server, I'm reposting the notes from my last blog about adding the FTP LDAP Authentication provider and adding authorization rules for FTP users or groups.

  1. Add the custom authentication provider for an FTP site:
    • Open an FTP site in the Internet Information Services (IIS) Manager.
    • Double-click FTP Authentication in the main window.
    • Click Custom Providers... in the Actions pane.
    • Check FtpLdapAuthentication in the providers list.
    • Click OK.
  2. Add an authorization rule for the authentication provider:
    • Double-click FTP Authorization Rules in the main window.
    • Click Add Allow Rule... in the Actions pane.
    • You can add either of the following authorization rules:
      • For a specific user:
        • Select Specified users for the access option.
        • Enter a user name that you created in your AD LDS partition.
      • For a role or group:
        • Select Specified roles or user groups for the access option.
        • Enter the role or group name that you created in your AD LDS partition.
      • Select Read and/or Write for the Permissions option.
    • Click OK.

Once these settings are configured and users connect to your FTP site, the FTP service will attempt to authenticate users from your LDAP server by using the custom FTP LDAP Authentication provider.


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

FTP and LDAP - Part 1: How to Use Managed Code (C#) to Create an FTP Authentication Provider that uses an LDAP Server

Over the past few years I've created a series of authentication providers for the FTP 7.5 service that ships with Windows Server 2008 R2 and Windows 7, and is available for download for Windows Server 2008. Some of these authentication providers are available on the http://learn.iis.net/page.aspx/590/developing-for-ftp-75/ website, while others have been in my blog posts.

With that in mind, I had a question a little while ago about using an LDAP server to authenticate users for the FTP service, and it seemed like that would make a great subject for another custom FTP authentication provider blog post.

The steps in this blog will lead you through the steps to use managed code to create an FTP authentication provider that uses a server running Active Directory Lightweight Directory Services (AD LDS) that is located on your local network.

Note: I wrote and tested the steps in this blog using both Visual Studio 2010 and Visual Studio 2008; if you use an different version of Visual Studio, some of the version-specific steps may need to be changed.

In This Blog

Prerequisites

The following items are required to complete the procedures in this blog:

  1. The following version of IIS must be installed on your Windows computer, and the Internet Information Services (IIS) Manager must also be installed:
    • IIS 7.0 must be installed on Windows Server 2008
    • IIS 7.5 must be installed on Windows Server 2008 R2 or Windows 7
  2. The new FTP 7.5 service must be installed. To install FTP 7.5, follow the instructions in the following topic:
  3. You must have FTP publishing enabled for a site. To create a new FTP site, follow the instructions in the following topic:
  4. You must have an AD LDS server available on your local network. Note: See my How to Set Up an Active Directory Lightweight Directory Services (AD LDS) Server blog post for more information.

Note: To test this blog, I used AD LDS on Windows Server 2008; if you use a different LDAP server, you may need to change some of the LDAP syntax in the code samples. To get started using AD LDS, see the following topics:

I tested this blog by using the user objects from both the MS-User.LDF and MS-InetOrgPerson.LDF Lightweight Directory interchange Format (LDIF) files.

Important

To help improve the performance for authentication requests, the FTP service caches the credentials for successful logins for 15 minutes by default. This means that if you change the password in your AD LDS server, this change may not be reflected for the cache duration. To alleviate this, you can disable credential caching for the FTP service. To do so, use the following steps:

  1. Open a command prompt.
  2. Type the following commands:
    cd /d "%SystemRoot%\System32\Inetsrv"
    Appcmd.exe set config -section:system.ftpServer/caching /credentialsCache.enabled:"False" /commit:apphost
    Net stop FTPSVC
    Net start FTPSVC
  3. Close the command prompt.

Step 1: Set up the Project Environment

In this step, you will create a project in Visual Studio 2008 for the demo provider.

  1. Open Microsoft Visual Studio 2008.
  2. Click the File menu, then New, then Project.
  3. In the New Projectdialog box:
    • Choose Visual C# as the project type.
    • Choose Class Library as the template.
    • Type FtpLdapAuthentication as the name of the project.
    • Click OK.
  4. When the project opens, add a reference path to the FTP extensibility library:
    • Click Project, and then click FtpLdapAuthentication Properties.
    • Click the Reference Paths tab.
    • Enter the path to the FTP extensibility assembly for your version of Windows, where C: is your operating system drive.
      • For Windows Server 2008 and Windows Vista:
        • C:\Windows\assembly\GAC_MSIL\Microsoft.Web.FtpServer\7.5.0.0__31bf3856ad364e35
      • For 32-bit Windows 7 and Windows Server 2008 R2:
        • C:\Program Files\Reference Assemblies\Microsoft\IIS
      • For 64-bit Windows 7 and Windows Server 2008 R2:
        • C:\Program Files (x86)\Reference Assemblies\Microsoft\IIS
    • Click Add Folder.
  5. Add a strong name key to the project:
    • Click Project, and then click FtpLdapAuthentication Properties.
    • Click the Signing tab.
    • Check the Sign the assembly check box.
    • Choose <New...> from the strong key name drop-down box.
    • Enter FtpLdapAuthenticationKey for the key file name.
    • If desired, enter a password for the key file; otherwise, clear the Protect my key file with a password check box.
    • Click OK.
  6. Note: FTP 7.5 Extensibility does not support the .NET Framework 4.0; if you are using Visual Studio 2010, or you have changed your default framework version, you may need to change the framework version. To do so, use the following steps:
    • Click Project, and then click FtpLdapAuthentication Properties.
    • Click the Application tab.
    • Choose .NET Framework 3.5 in the Target framework drop-down menu.
    • Save, close, and re-open the project.
  7. Optional: You can add a custom build event to add the DLL automatically to the Global Assembly Cache (GAC) on your development computer:
    • Click Project, and then click FtpLdapAuthentication Properties.
    • Click the Build Events tab.
    • Enter the appropriate commands in the Post-build event command linedialog box, depending on your version of Visual Studio:
      • If you are using Visual Studio 2010:
        net stop ftpsvc
        call "%VS100COMNTOOLS%\vsvars32.bat">null
        gacutil.exe /if "$(TargetPath)"
        net start ftpsvc
      • If you are using Visual Studio 2008:
        net stop ftpsvc
        call "%VS90COMNTOOLS%\vsvars32.bat">null
        gacutil.exe /if "$(TargetPath)"
        net start ftpsvc
      Note: You need to be logged in as an administrator in order to restart the service and add the dll to the Global Assembly Cache.
  8. Save the project.

Step 2: Create the Extensibility Class

In this step, you will implement the authentication and role extensibility interfaces for the demo provider.

  1. Add the necessary references to the project:
    • Click Project, and then click Add Reference...
    • On the .NET tab, click Microsoft.Web.FtpServer.
    • Click OK.
    • Repeat the above steps to add the following references to the project:
      • System.Configuration
      • System.DirectoryServices
      • System.DirectoryServices.AccountManagement
  2. Add the code for the authentication class:
    • In Solution Explorer, double-click the Class1.cs file.
    • Remove the existing code.
    • Paste the following code into the editor:
      using System;
      using System.Collections.Specialized;
      using System.Configuration.Provider;
      using System.DirectoryServices;
      using System.DirectoryServices.AccountManagement;
      using Microsoft.Web.FtpServer;
      
      public class FtpLdapAuthentication :
        BaseProvider,
        IFtpAuthenticationProvider,
        IFtpRoleProvider
      {
        private static string _ldapServer = string.Empty;
        private static string _ldapPartition = string.Empty;
        private static string _ldapAdminUsername = string.Empty;
        private static string _ldapAdminPassword = string.Empty;
      
        // Override the default initialization method.
        protected override void Initialize(StringDictionary config)
        {
          // Retrieve the provider settings from configuration.
          _ldapServer = config["ldapServer"];
          _ldapPartition = config["ldapPartition"];
          _ldapAdminUsername = config["ldapAdminUsername"];
          _ldapAdminPassword = config["ldapAdminPassword"];
      
          // Test for the LDAP server name (Required).
          if (string.IsNullOrEmpty(_ldapServer) || string.IsNullOrEmpty(_ldapPartition))
          {
            throw new ArgumentException(
              "Missing LDAP server values in configuration.");
          }
        }
      
        public bool AuthenticateUser(
          string sessionId,
          string siteName,
          string userName,
          string userPassword,
          out string canonicalUserName)
        {
          canonicalUserName = userName;
          // Attempt to look up the user and password.
          return LookupUser(true, userName, string.Empty, userPassword);
        }
      
        public bool IsUserInRole(
          string sessionId,
          string siteName,
          string userName,
          string userRole)
        {
          // Attempt to look up the user and role.
          return LookupUser(false, userName, userRole, string.Empty);
        }
      
        private static bool LookupUser(
          bool isUserLookup,
          string userName,
          string userRole,
          string userPassword)
        {
          PrincipalContext _ldapPrincipalContext = null;
          DirectoryEntry _ldapDirectoryEntry = null;
      
          try
          {
            // Create the context object using the LDAP connection information.
            _ldapPrincipalContext = new PrincipalContext(
              ContextType.ApplicationDirectory,
              _ldapServer,
      
              _ldapPartition,
              ContextOptions.SimpleBind,
              _ldapAdminUsername,
              _ldapAdminPassword);
      
            // Test for LDAP credentials.
            if (string.IsNullOrEmpty(_ldapAdminUsername) || string.IsNullOrEmpty(_ldapAdminPassword))
            {
              // If LDAP credentials do not exist, attempt to create an unauthenticated directory entry object.
              _ldapDirectoryEntry = new DirectoryEntry("LDAP://" + _ldapServer + "/" + _ldapPartition);
            }
            else
            {
              // If LDAP credentials exist, attempt to create an authenticated directory entry object.
              _ldapDirectoryEntry = new DirectoryEntry("LDAP://" + _ldapServer + "/" + _ldapPartition,
                _ldapAdminUsername, _ldapAdminPassword, AuthenticationTypes.Secure);
            }
      
            // Create a DirectorySearcher object from the cached DirectoryEntry object.
            DirectorySearcher userSearcher = new DirectorySearcher(_ldapDirectoryEntry);
            // Specify the the directory searcher to filter by the user name.
            userSearcher.Filter = String.Format("(&(objectClass=user)(cn={0}))", userName);
            // Specify the search scope.
            userSearcher.SearchScope = SearchScope.Subtree;
            // Specify the directory properties to load.
            userSearcher.PropertiesToLoad.Add("distinguishedName");
            // Specify the search timeout.
            userSearcher.ServerTimeLimit = new TimeSpan(0, 1, 0);
            // Retrieve a single search result.
            SearchResult userResult = userSearcher.FindOne();
            // Test if no result was found.
            if (userResult == null)
            {
              // Return false if no matching user was found.
              return false;
            }
            else
            {
              if (isUserLookup == true)
              {
                try
                {
                  // Attempt to validate credentials using the username and password.
                  return _ldapPrincipalContext.ValidateCredentials(userName, userPassword, ContextOptions.SimpleBind);
                }
                catch (Exception ex)
                {
                  // Throw an exception if an error occurs.
                  throw new ProviderException(ex.Message);
                }
              }
              else
              {
                // Retrieve the distinguishedName for the user account.
                string distinguishedName = userResult.Properties["distinguishedName"][0].ToString();
      
                // Create a DirectorySearcher object from the cached DirectoryEntry object.
                DirectorySearcher groupSearcher = new DirectorySearcher(_ldapDirectoryEntry);
                // Specify the the directory searcher to filter by the group/role name.
                groupSearcher.Filter = String.Format("(&(objectClass=group)(cn={0}))", userRole);
                // Specify the search scope.
                groupSearcher.SearchScope = SearchScope.Subtree;
                // Specify the directory properties to load.
                groupSearcher.PropertiesToLoad.Add("member");
                // Specify the search timeout.
                groupSearcher.ServerTimeLimit = new TimeSpan(0, 1, 0);
                // Retrieve a single search result.
                SearchResult groupResult = groupSearcher.FindOne();
      
                // Loop through the member collection.
                for (int i = 0; i < groupResult.Properties["member"].Count; ++i)
                {
                  string member = groupResult.Properties["member"][i].ToString();
                  // Test if the current member contains the user's distinguished name.
                  if (member.IndexOf(distinguishedName, StringComparison.OrdinalIgnoreCase) > -1)
                  {
                    // Return true (role lookup succeeded) if the user is found.
                    return true;
                  }
                }
                // Return false (role lookup failed) if the user is not found for the role.
                return false;
              }
            }
          }
          catch (Exception ex)
          {
            // Throw an exception if an error occurs.
            throw new ProviderException(ex.Message);
          }
        }
      }
  3. Save and compile the project.

Note: If you did not use the optional steps to register the assemblies in the GAC, you will need to manually copy the assemblies to your IIS 7 computer and add the assemblies to the GAC using the Gacutil.exe tool. For more information, see the following topic on the Microsoft MSDN Web site:

Global Assembly Cache Tool (Gacutil.exe)

Step 3: Add the Demo Provider to FTP

In this step, you will add your provider to the list of providers for your FTP service, configure your provider for your LDAP server, and enable your provider to authenticate users for an FTP site.

Adding your Provider to FTP

  1. Determine the assembly information for your extensibility provider:
    • In Windows Explorer, open your "C:\Windows\assembly" path, where C: is your operating system drive.
    • Locate the FtpLdapAuthentication assembly.
    • Right-click the assembly, and then click Properties.
    • Copy the Culture value; for example: Neutral.
    • Copy the Version number; for example: 1.0.0.0.
    • Copy the Public Key Token value; for example: 426f62526f636b73.
    • Click Cancel.
  2. Add the extensibility provider to the global list of FTP authentication providers:
    • Open the Internet Information Services (IIS) Manager.
    • Click your computer name in the Connections pane.
    • Double-click FTP Authentication in the main window.
    • Click Custom Providers... in the Actions pane.
    • Click Register.
    • Enter FtpLdapAuthentication for the provider Name.
    • Click Managed Provider (.NET).
    • Enter the assembly information for the extensibility provider using the information that you copied earlier. For example:
      FtpLdapAuthentication,FtpLdapAuthentication,version=1.0.0.0,Culture=neutral,PublicKeyToken=426f62526f636b73
    • Click OK.
    • Clear the FtpLdapAuthentication check box in the providers list.
    • Click OK.

Configuring your Provider's Settings

  1. Determine the connection information for your LDAP server; there are four pieces of information that you will need to know in order to configure the extensibility provider to talk to your LDAP server:
    • Server Name and TCP/IP Port: This is the name (or IP address) of the server that is hosting your LDAP service; the port is usually 389. These will be added to your provider using the "SERVERNAME:PORT" syntax.
    • LDAP Partition: This is the LDAP path within your LDAP service to your data, for example: "CN=ServerName,DC=DomainName,DC=DomainExtension."
    • LDAP Username: This is a username that has access to your LDAP server; this is not the name of an account that you will use for FTP access, and it does not have to be a Windows account.
    • LDAP Password: This is the password that is associated with the LDAP username.
  2. Using the information from the previous steps, configure the options for the provider:
    • At the moment there is no user interface that enables you to add properties for a custom authentication module, so you will have to use the following command line. You will need to update the highlighted areas with the information from the previous steps and the information for your LDAP server:
      cd %SystemRoot%\System32\Inetsrv

      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication']" /commit:apphost

      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication'].[key='ldapServer',value='MYSERVER:389']" /commit:apphost

      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication'].[key='ldapPartition',value='CN=MyServer,DC=MyDomain,DC=local']" /commit:apphost

      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication'].[key='ldapAdminUsername',encryptedValue='MyAdmin']" /commit:apphost

      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpLdapAuthentication'].[key='ldapAdminPassword',encryptedValue='MyPassword1']" /commit:apphost
    • Note: The highlighted areas are the values for the ldapServer, ldapPartition, ldapAdminUsername, and ldapAdminPassword settings, which configure your network environment for your LDAP server.

Enabling your Provider for an FTP site

  1. Add the custom authentication provider for an FTP site:
    • Open an FTP site in the Internet Information Services (IIS) Manager.
    • Double-click FTP Authentication in the main window.
    • Click Custom Providers... in the Actions pane.
    • Check FtpLdapAuthentication in the providers list.
    • Click OK.
  2. Add an authorization rule for the authentication provider:
    • Double-click FTP Authorization Rules in the main window.
    • Click Add Allow Rule... in the Actions pane.
    • You can add either of the following authorization rules:
      • For a specific user:
        • Select Specified users for the access option.
        • Enter a user name that you created in your AD LDS partition.
      • For a role or group:
        • Select Specified roles or user groups for the access option.
        • Enter the role or group name that you created in your AD LDS partition.
      • Select Read and/or Write for the Permissions option.
    • Click OK.

Summary

In this blog I showed you how to:

  • Create a project in Visual Studio 2010 or Visual Studio 2008 for a custom FTP authentication provider.
  • Implement the extensibility interface for custom FTP authentication.
  • Add a custom authentication provider to your FTP service.

When users connect to your FTP site, the FTP service will attempt to authenticate users from your LDAP server by using your custom authentication provider.

Additional Information

The PrincipalContext.ValidateCredentials() method will validate the user name in the userName parameter with the value of the userPrincipalName attribute of the user object in AD LDS. Because of this, the userPrincipalName attribute for a user object is expected to match the name of the user account that an FTP client will use to log in, which will should be the same value as the cn attribute for the user object. Therefore, when you create a user object in AD LDS, you will need to set the corresponding userPrincipalName attribute for the user object. In addition, when you create a user object in AD LDS, the msDS-UserAccountDisabled attribute is set to TRUE by default, so you will need to change the value of that attribute to FALSE before you attempt to log in.

For more information, see my follow-up blog that is titled FTP and LDAP - Part 2: How to Set Up an Active Directory Lightweight Directory Services (AD LDS) Server.


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/