8.2 An FTP/HTTP Combination

The first set of services that we will explore is the combination of the ProFTPD server (http://www.proftpd.org/) and Apache (http://www.apache.org/). In this scenario, we would like to build a new web server and allow users to publish web content using an FTP client. All user and group accounts already exist in the LDAP directory, but just to make things interesting, assume that your theoretical web server platform cannot make use of either PAM or NSS to access any of this information.

The solution we would like to deploy is illustrated in Figure 8-2. Users should be able to put files into ~<username>/public_html on the web server using FTP. ProFTPD must authenticate user connections using information stored in the LDAP directory. These files should then be accessible via a web browser at http://www.plainjoe.org/~<username>. Because the server is not using an nss_ldap library, Apache must obtain the home directory path for users directly from the LDAP server.

Figure 8-2. Interaction between ProFTPD, Apache, and the LDAP directory on www.plainjoe.org

Two add-ins, both developed by John Morrissey (http://www.horde.net/~jwm), will help you implement your new web server. We will begin by looking at ProFTPD's LDAP features.

8.2.1 ProFTPD

Morrissey's LDAP authentication module (mod_ldap) is included with current releases of the ProFTPD server.[2]

[2] ProFTPD's mod_ldap module should not be confused with any of Apache's modules of the same name (http://modules.apache.org/).

Our focus will be on the ProFTPD Release v1.2.7. Building mod_ldap is controlled by an option to the configure script (?with-modules=mod_ldap). After extracting the source code, the following commands build the binaries and tools to include support for mod_ldap:

$ ./configure --with-modules=mod_ldap
$ make 
$ /bin/su -c "make install"

You can specify multiple modules as arguments to the ?with-modules option by separating each module name in the list with a colon (e.g., ?with-modules=mod_ldap:mod_linuxprivs). For more information on the various modules supported by the ProFTPD package, refer to the documentation included with the software or the online versions at http://www.proftpd.org/docs/.

ProFTPD's -l command-line option can be used to verify that the mod_ldap module was included in the final binary. The actual list of modules may vary depending on the OS used during compilation, but at a minimum, you must see mod_ldap listed:

$ proftpd -l
Compiled-in modules:

The sample-configurations/ subdirectory included in the source distribution contains settings for several common scenarios. We'll use the following proftpd.conf file as starting point. The parameters are fairly self-explanatory, but you can review the configuration documentation included in the docs/ directory of the proftpd source distribution if you need more information.

## ###################################################
## ProFTPD configuration file 
## (/usr/local/etc/proftpd.conf)
## Global directives
## ###################################################
ServerType              standalone
DefaultServer           on
Port                    21
Umask                   022
User                    nobody
Group                   nobody
## <- LDAP parameters will go here. ->
# Normally, files should be overwritable.
<Directory /*>
  AllowOverwrite                on

Your first step is to restrict a user to her individual ~/public_html subdirectory when connecting. We will assume that this directory already exists. The DefaultRoot option defines the path that will be passed to the chroot( ) call by the proftpd server.

## Limit users to their web directory.
DefaultRoot     ~/public_html

The next step is to instruct the proftpd daemon to use the mod_ldap module for authentication. The LDAPDoAuth keyword accepts up to three arguments. The first either turns the module on or off. The second argument is the base suffix to use when searching the directory. The final argument allows an administrator to define a customized search filter. In the absence of an explicit filter string, the default of (&(uid=%u)(objectclass=posixAccount)) is used.

## Limit users to their web directory. Use the default search filter.
LDAPDoAuth     on "ou=people,dc=plainjoe,dc=org"

Of course, you also must define the hostname of the directory server:

## Define the LDAP server to contact. 
LDAPServer     ldap.plainjoe.org

By default, the ProFTPD daemon uses an anonymous bind when searching an LDAP directory. However, if you include the LDAPDNInfo directive in the configuration file, the daemon uses a DN and password to bind to the LDAP server. We'll stick with anonymous binds since Chapter 6 allowed nss_ldap clients to enumerate account information this way.

The mod_ldap module supports two means of authenticating user connections once their directory entries are located. The LDAPAuthBinds directive controls which method is used. If it is set to off, mod_ldap searches the LDAP server anonymously (or uses a simple bind as the LDAPDNInfo entry) to retrieve all of the user information including the userPassword attribute. The module then hashes the password entered by the user (if necessary) using the local system's crypt( ) function and compares it to the value obtained from the directory search. This means that the userPassword must be stored in either {CLEAR} or {CRYPT} formats.

The preferred and default method (LDAPAuthBinds on) authenticates the connecting user by binding to the directory server. In this case, ProFTPD locates the DN of the connecting user by searching the directory (either anonymously or as the LDAPDNInfo). However, the userPassword attribute is never requested from the LDAP server under this configuration. The module then binds to the directory again, using the user's DN and the password that the user entered. If this bind succeeds, ProFTPD considers the user to be authenticated.

To configure ProFTPD for the preferred authentication method, add the following line to proftpd.conf:

## Require that an incoming user can successfully bind to the LDAPServer.
LDAPAuthBinds     on

The final hurdle to overcome is to inform proftpd how to resolve UIDs and GIDs when listing files without using the standard getpwuid( ) and getgrgid( ) calls. The LDAPDoUIDLookups and LDAPDoGIDLookups directives instruct proftpd to query the directory server using the specified base suffix. Each directive accepts an optional parameter if you find it necessary to override the respective default search filters of (&(uidNumber=UNIX uid)(objectclasses=posixAccount)) and (&(gidNumber=UNIX gid) (objectclasses=posixGroup)). These filters work well with your directory, so there is no need to change them.

## Look up UIDs and GIDs in the directory.
LDAPDoGIDLookups   on "ou=group,dc=plainjoe,dc=org"
LDAPDoUIDLookups   on "ou=people,dc=plainjoe,dc=org"

Assuming that you have a valid user named kristi in the directory, you can verify that mod_ldap and proftpd are working by connecting to the server (www.plainjoe.org) and uploading a file:

$ ncftp -u kristi -p testpass www.plainjoe.org
NcFTP 3.1.3 (Mar 27, 2002) by Mike Gleason (ncftp@ncftp.com).
Connecting to
ProFTPD 1.2.7 Server (ProFTPD Default Installation) [www.plainjoe.org]
Logging in . . . 
User kristi logged in.
Logged in to localhost.
ncftp / > put index.html
index.html:                                1.38 kB   69.43 kB/s
ncftp / > ls -l
-rw-r--r--   1 kristi   ftpusers    464 Dec 18  2002 index.html

Table 8-1 lists the entire set of directives for mod_ldap.

Table 8-1. Parameters for the ProFTPD mod_ldap 2.7 module






Should the connecting user be authenticated by binding to the directory server using the located DN and the user's password (on), or should the module hash the password locally and compare it with the userPassword attribute obtained from the directory (off)?



Specifies the hashing scheme for passwords that are not prefixed by a type string ({ }). Possible values are crypt and clear.



Specifies the default Unix GID to be assigned to the user if the gidNumber attribute is unavailable.



Specifies the default Unix UID to be assigned to the user if the uidNumber attribute is unavailable.


"" ""

Defines the DN and password to use when binding to the directory server for searches.



Should mod_ldap be enabled for authentication?



Should mod_ldap attempt to resolve GID numbers to names by querying the directory for matching posixGroup entries?



Should mod_ldap attempt to resolve UID numbers to names by querying the directory for matching posixAccount entries?



Forces the GID of all connected users to the LDAPDefaultGID, even if a gidNumber attribute can be obtained.



Forces the UID of all connected users to the LDAPDefaultUID, even if a uidNumber attribute can be obtained.



Instructs mod_ldap to create the user's home directory (from the homeDirectory attribute) if it does not already exist. The directive also accepts a second parameter that sets the mode of the new directory.



Specifies additional subdirectories to be created in the event that LDAPHomedirOnDemand has been enabled. Multiple directories can be included in a whitespace-delimited list.



Instructs mod_ldap to cache negative responses to UID/GID resolution attempts.


LDAP client library default

Specifies the maximum amount of time, in seconds, to wait for a search to complete.



Defines the LDAP search scope as onelevel or subtree.



Specifies the hostname of the directory server. An alternative to port 389 can be defined using the syntax server:port. Multiple servers can be specified; separate server hostnames by spaces.



This parameter is available only if mod_ldap.c has been modified to define USE_LDAPV3_TLS. If enabled, mod_ldap will use the StartTLS extension when contacting the LDAP server. If the directory does not support TLS, mod_ldap will downgrade to an unencrypted channel and simply report failure to the proftpd server.

8.2.2 Apache

Now that users can upload files to your web server, Apache must be configured to resolve URLs such as http://www.plainjoe.org/~kristi. Traditionally, Apache administrators have used a subdirectory named public_html in home directories to provide a simple mechanism for users to publish personal web pages. This associates the tilde (~) with a home directory by asking the operating system to provide the details about the user from the local system password file, NIS map, or LDAP directory via NSS modules.

Because we have chosen not to implement any nss_ldap functionality on the server, we will have to use another means of instructing Apache how to determine a user's home directory location. Morrissey's mod_ldap_userdir module allows us to do just that.

This module obtains the path to a user's home directory by searching an LDAP directory for a posixAccount entry with a matching uid value. Our LDAP directory already supports the schema required for mod_ldap_userdir, so the new work to be done is localized to the web server. As usual, we focus only on the aspects of Apache needed to integrate the server with an LDAP directory. Full coverage of Apache configuration is well beyond the scope of a single chapter, as is the case with all of the server packages discussed in this chapter. For more information on Apache and its httpd.conf, refer to Apache: The Definitive Guide, by Ben and Peter Laurie (O'Reilly).

The first step is to download the latest version of the module from http://www.horde.net/~jwm/software/mod_ldap_userdir/. Building mod_ldap_userdir requires adding only a single option (?with-activate) to the configure script. However, unless the Apache eXtenSion tool is located in a directory in your $PATH, it will also be necessary to set the absolute path to the apxs binary. These are the steps I used to build the module for an Apache 1.3.23 installation, although an Apache 2.0 installation is no different:

$ ./configure --with-activate --with-apxs=/usr/sbin/apxs
$ make 
$ /bin/su -c "make install"
/usr/sbin/apxs -i -a mod_ldap_userdir.so
[activating module 'ldap_userdir' in /etc/httpd/conf/httpd.conf]
cp mod_ldap_userdir.so /usr/lib/apache/mod_ldap_userdir.so
chmod 755 /usr/lib/apache/mod_ldap_userdir.so
cp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.bak
cp /etc/httpd/conf/httpd.conf.new /etc/httpd/conf/httpd.conf
rm /etc/httpd/conf/httpd.conf.new

The build process will fail if configure cannot locate the necessary LDAP libraries and header files. The two options, ?with-sdk-headers=<path> and ?with-sdk-libs=<path>, can be used to specify the path to the LDAP SDK header files and libraries. The final make install command should copy the compiled library to the directory containing the other installed Apache modules (normally /usr/lib/apache/) and activate the module in httpd.conf. Here is the LoadModule line created in Apache's configuration from the installation (the comments are my own):

## Activate the LDAP userdir module (this may also require an AddModule 
## mod_ldap_userdir.c directive later in the file depending on the server's
## configuration).
LoadModule ldap_userdir_module /usr/lib/apache/mod_ldap_userdir.so

The module itself has seven directives, which are presented in Table 8-2. Your web server uses four of these directives:

<IfModule mod_ldap_userdir.c>
   LDAPUserDirServer       ldap.plainjoe.org
   LDAPUserDirSearchScope  subtree
   LDAPUserDirBaseDN       ou=people,dc=plainjoe,dc=org
   LDAPUserDir             public_html

Table 8-2. Directives for mod_ldap_userdir






The expected name of the subdirectory.



The hostname of the LDAP directory server.



The DN and password to be used to bind to the directory. The password should be given in clear text.



The base search suffix to use when searching the directory.



The RFC 2254-compliant LDAP search filter to use when querying the directory.



The scope of the LDAP search; can be a onelevel or subtree.



Whether to use the StartTLS extended operation (on) or an unencrypted connection (off) when searching the directory.

The values for each directive in your configuration are fairly self-explanatory. LDAPUserDirServer, LDAPUserDirSearchScope, and LDAPUserDirBaseDN set the standard LDAP search parameters: the server's hostname (ldap.plainjoe.org), the search scope (subtree), and the base suffix (ou=people,dc=plainjoe,dc=org). The search filter is not set explicitly because the default filter string, (&(uid=%v)(objectclass=posixAccount)), works nicely with your directory; it matches the current username against the uid attributes of all posixAccount objects.

By default, Apache binds to the directory anonymously. However, you could specify a DN and password to be used when binding to the LDAP server by defining the LDAPUserDirDNInfo parameter. There is no need to avoid anonymous searches in this case because the uid and homeDirectory attributes have already been made available anonymously to support other services such as ProFTPD.

Once all of these pieces are in place, you can verify that the module is working correctly by viewing the index.html file uploaded to ~kristi/public_html in the previous section. If there are any errors, the two places to look for clues are Apache's error_log and OpenLDAP's syslog messages.