Subject: WU FTPD and LDAP

From: Jos Visser <[email-masked]>
Date: Sun, 22 Aug 1999 13:12:19 +0200

Hi,

And thus it came to pass that I felt this pressing need to integratie
WU-FTPD with LDAP without any intervention from PAM. I have looked
around a bit but did not find an out-of the box solution that I could
use. I therefore decided to write my own. It seems to be working
allright (tested on Linux and HP-UX). I would like to share this code
with the WU-FTPD community so I include the files of interest with this
e-mail message.

Please beware that this code has a serious "0.1-under-development"
status. Please heed the warnings in README.LDAP! 
I would be interested to hear what people think and where it can be
improved. I would be happy to take in requests and see if, when and how
they could be implemented.

With this e-mail I include a recursive diff of the 'wu-ftpd-2.5.0' and
'wu-ftpd-2.5.0-ldap' directories. New/changed files in the
wu-ftpd-2.5.0-ldap directory are reflected in the diff output.
Currently, I have updated the Linux makefile only (my development
platform), modifications for HP-UX are underway. Included in the patch
is a README.LDAP file that explains some of the mechanisms employed.

Share and Enjoy! (motto of the Sirius Cybernetics Corporation :-)

++Jos

=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT
diff -N -r wu-ftpd-2.5.0/README.LDAP wu-ftpd-2.5.0-ldap/README.LDAP
0a1,235
> ===========
> README.LDAP                   Jos Visser ([email-masked])
> ===========
> 
> Date: Fri Aug 20 16:01:14 MEST 1999
> 
> Introduction
> ------------
> And thus it came to pass that for an extranet project I am working on I
> felt this pressing need to have the WU FTP daemon lookup and
> authenticate users in an LDAP compliant directory service (e.g. the
> Netscape Directory Server). After having looked around on the Internet,
> and investigating things like PAM, I decided to extend WU FTP with an
> LDAP capability. Since you're reading this file, I presume that you want
> to know how it works.
> 
> First of all, the current capability is *very* basic, but, it is exactly
> what I need and can relatively easily be extended. An important thing
> that is currently missing is connecting over SSL (port 636). But, who
> knows what the future will hold?
> 
> Why?
> ----
> Good question. Traditionally, FTP knows two types of users: anonymous
> users and real users. The anonymous user has a virtual user name
> ("anonymous" or "ftp"), whereas real users must be present in the system
> password file. WU FTP has a "guest user" capability which restricts a
> real user to his home directory. However, the user and password are
> still maintained in the system password file (/etc/passwd).
> 
> In my project, I have an extranet application that uses a mix of web and
> FTP to up-and-download files to and from an Internet server. Both web
> and FTP access are restricted to authenticated and authorised users. I
> want to use the same user administration for web and FTP access, and I
> do not want the users to be in the Unix password file (for security and
> other reasons).
> 
> My web server is a Netscape Enterprise (web) Server which has built-in
> capabilities to use an LDAP directory server. So, all I lack is LDAP
> support in my FTP daemon.
> 
> Building WU FTP with LDAP
> -------------------------
> The following description assumes that you're a code warrior who has
> build, installed and configured WU FTP before.
> 
> To build WU FTP with LDAP support you need:
> 
> - WU FTP 2.5.0
> - a patched version of src/ftpd.c
> - ldapftp.c
> - patched makefiles for the various platforms (currently, I have the
>   Linux and HP-UX makefiles updated, because these are my development
>   and production platforms).
> - The OpenLDAP LDAP SDK
> 
> First, build OpenLDAP conform its instructions. We only need the LDAP
> SDK libraries, but why not build the OpenLDAP directory server and play
> with it while your at it?
> 
> Then, build WU FTP with LDAP support:
> 
> ./build "USE_LDAP=1 LDAP_INCLUDE=/path/to/openldap/include LDAP_LIBS=/path/to/src/openldap/libraries" <platform>
> 
> If you're really lucky, everything builds. Mind you, currently this is
> all development code. It builds on my laptop (SuSE Linux 6.1), that's
> all I can currently promise. What is especially the case is that you
> currently need an ANSI C compiler because I use prototypes and I haven't
> yet found the time to throw in the #ifdefs to make it compile under both
> K&R C and ANSI C.
> 
> After build, just run "./build install" and you're set to go.
> 
> How does the WU FTP daemon use LDAP?
> ------------------------------------
> The core of WU FTP is in the file "src/ftpd.c". When an FTP user sends
> his/her user name, WU FTP normally uses "getpwnam" to retrieve user
> information such as the uid, gid and home directory. When this fails
> user authentication fails. In the patched ftpd.c compiled with
> -DUSE_LDAP, ftpd.c then calls out to "getpwnam_ldap()" (in ldapftp.c)
> which tries to look up the user in the directory. If this succeeds, the
> user information is retrieved from the directory and then copied in an
> industry standard "struct passwd". 
> 
> If the user then sends his/her password (and the user information was
> looked up using LDAP), the routine "check_pass_ldap()" is called to
> check the password against the password field in the directory. 
> 
> Please beware that the patched "ftpd.c" *first* looks up the user using
> the "old" lookup scheme (anonymous, system password file), and only when
> this fails it employs LDAP to lookup the user in the directory. So it
> remains possible to FTP in using a real user!
> 
> Furthermore, LDAP can be employed *together* with the "guest user"
> facility. WU FTP with LDAP retrieves the numeric uid and gid from the
> directory, and then the ordinary "ftpaccess" statements like "guestuser"
> and "guestgroup" are checked to see if access restrictions apply. Most
> commonly you would want to use "guest access" together with LDAP!
> 
> Configuring WU FTP for LDAP access
> ----------------------------------
> All necessary parameters for the LDAP connection have to be specified in
> the "ftpaccess" file. A couple of new ftpaccess statements have been
> added that allow the administrator to specify the connection parameters:
> 
> ldap-host     ldap.osp.nl
> ldap-port     389
> ldap-base     o=osp.nl
> ldap-user     uid=dirmgr, ou=Directory Users, o=osp.nl
> ldap-password secret
> 
> Sensible defaults exist for these parameters, so you do not need to
> specify anything. However, the "ldap-user" used in the LDAP connection
> must have enough permission to search the directory, retrieve user
> information and compare the user password to the value stored in the
> directory.
> 
> Configuring the directory for WU FTP
> ------------------------------------
> WU FTP looks up users with the search filter "uid=username". The user
> entries for which you want to allow FTP access *must* be of object class
> "ftpUser". Presumably this object class does not exist in your
> directory. The solution: add it using your directories extensible schema
> editor.
> 
> WU FTP expects the following attributes to be present in the user entry:
> 
> - uid                 the user name (string)
> - userPassword        (string)
> - uidNumber   the numeric userid (a la Unix, integer)
> - gidNumber   the numeric groupid (a la Unix, integer)
> - homeDirectory       the user's home directory (string)
> 
> The following attributes may be present:
> 
> - cn          the common name of the user (string)
> - gecos               a standard Unix gecos field (string)
> 
> You need to set up permissions in the directory so that the user that WU
> FTP uses has enough permissions to search through the directory,
> retrieve the attributes specified before *and* have "compare" permission
> to the userPassword field.
> 
> Regarding user password storage. WU FTP tries SHA, MD5 and clear text
> passwords before it gives up.
> 
> Reference
> ---------
> There is tons of stuff on LDAP out there. From personal experience I can
> recommend the book "LDAP -- programming directory enabled applications
> with lightweight directory access protocol" by Tim Howes and Mark Smith.
> 
> Example
> -------
> The following is the output of a series of Unix commands that show how
> things work (everything following // are my comments):
> 
> // First I perform an ldapsearch to show you the entry of user test in
> // the directory
> 
> $ ldapsearch -b o=osp.nl uid=test
> uid=test,o=OSP,ou=extranet,o=osp.nl
> objectclass=top
> objectclass=person
> objectclass=organizationalPerson
> objectclass=inetOrgPerson
> objectclass=ftpUser           // You see, this is an FTP user
> cn=Test User
> uid=test
> givenname=Test
> sn=User
> creatorsname=uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot
> createtimestamp=19990819205615Z
> homedirectory=/home/josv/tmp
> uidnumber=1003                        // Pay attention here!
> gidnumber=1003
> modifiersname=uid=dirmgr,ou=Directory Users,o=osp.nl
> modifytimestamp=19990820093201Z
> 
> // Please note that you do not see the userPassword field in the entry
> // above. This is because the anonymous LDAP user that I used in the
> // ldapsearch does not have read permission for this attribute.
> 
> // Now I am going to perform an FTP
> $ ftp localhost
> Connected to localhost.
> 220 jadzia.josv.com FTP server (Version wu-2.5.0(1) Fri Aug 20 08:52:14 MEST 1999) ready.
> Name (localhost:josv): test   // This is a directory user!
> 331 Password required for test.
> Password:
> 230-Welcome Test User         // This comes from the "cn" attribute
> 230-Please read the file README
> 230-  it was last modified on Fri Apr  2 08:19:31 1999 - 140 days ago
> 230 User test logged in.  Access restrictions apply.  // Pay attention!
> Remote system type is UNIX.
> Using binary mode to transfer files.
> ftp> pwd
> 257 "/" is current directory.                         // Pay attention!
> ftp> quit
> 221-You have transferred 0 bytes in 0 files.
> 221-Total traffic for this session was 431 bytes in 0 transfers.
> 221-Thank you for using the FTP service on jadzia.josv.com.
> 221 Goodbye.
> 
> // Well, that spoke for itself I reckon....
> 
> // And we have logged something in the syslog
> 
> $ tail /var/log/messages
> Aug 20 16:36:43 jadzia wu.ftpd[8722]: connect from [email-masked]
> Aug 20 16:36:45 jadzia ftpd[8722]: test recognised as uid=test,o=OSP,ou=extranet,o=osp.nl (Test User)
> 
> About the example
> -----------------
> In this example, I used the WU FTP guest user facility in combination
> with WU FTP LDAP. In the directory the numeric uid and gid of the user
> "test" are 1003. This is the actual uid and gid of a user and group in
> the system:
> 
> In /etc/passwd:
> extraftp:x:1003:1003:Extranet FTP user:/not/exist:/no/shell
> 
> In /etc/group:
> extraftp:x:1003:extraftp
> 
> So, the FTP daemon assumes the identity of the "extraftp" Unix user.
> Furthermore, in "/etc/ftpaccess" we have:
> 
> guestuser %1003
> 
> So, after having authenticated the user, WU FTP fires up its normal
> "guest" facilities, doing a chroot to the user's home directory (from
> the directory (/home/josv/tmp)) and restricts the user to that
> directory. Neat uh?
> 
diff -N -r wu-ftpd-2.5.0/m wu-ftpd-2.5.0-ldap/m
0a1,2
> cd /home/josv/src/wu-ftpd-2.5.0
> ./build "USE_LDAP=1 LDAP_INCLUDE=/home/josv/src/openldap/include LDAP_LIBS=/home/josv/src/openldap/libraries" lnx
diff -N -r wu-ftpd-2.5.0/src/ftpd.c wu-ftpd-2.5.0-ldap/src/ftpd.c
513a514,519
> #ifdef USE_LDAP
> struct passwd *getpwnam_ldap(char *name);
> int check_pass_ldap(char *passwd);
> int ldap_user=0;
> #endif
> 
1368c1374,1384
<     if ((p = getpwnam(name)) == NULL)
---
> #ifdef USE_LDAP
>     ldap_user=0;
> #endif /* USE_LDAP */
>     if ((p = getpwnam(name)) == NULL) {
> #ifdef USE_LDAP
>       p=getpwnam_ldap(name);
>       
>       if (!p) return 0;
> 
>       ldap_user=1;
> #else /* !USE_LDAP */
1369a1386,1387
> #endif /* USE_LDAP */
>     }
2355a2374,2378
> #ifdef USE_LDAP
>       if (ldap_user)
>           if (pw && check_pass_ldap(passwd)) 
>               rval=0;
> #endif
diff -N -r wu-ftpd-2.5.0/src/ldapftp.c wu-ftpd-2.5.0-ldap/src/ldapftp.c
0a1,547
> /*
>  * (c) Copyright 1999 Open Solution Providers
>  *
>  * Author: Jos Visser ([email-masked])
>  *
>  * This software is provided under the GNU Public License (GPL). Contact
>  * me ([email-masked]) if you have no idea what that means, and we'll work on
>  * your education a bit :-).
>  *
>  * This software is provided "as is", with all flaws, bugs, errors and
>  * hidden features that are evident from close scrutiny of the source
>  * code.
>  *
>  * This file is the heart of the WU-FTP LDAP feature that I implemented
>  * because I felt that I needed it. Please feel free to adopt, adapt and
>  * improve this source, but send me ([email-masked]) a note when you do.
>  *
>  * For more elaborate documentation, see README.LDAP.
>  */
> 
> #ifdef USE_LDAP
> 
> #include <unistd.h>
> 
> #include "portable.h"
> 
> #include <stdlib.h>
> 
> #include <ac/string.h>
> #include <ac/unistd.h>
> 
> #include "lutil_md5.h"
> #include "lutil_sha1.h"
> #include "lutil.h"
> 
> #include <lber.h>
> #include <ldap.h>
> 
> #include <sys/time.h>
> #include <pwd.h>
> #include <syslog.h>
> 
> #include "extensions.h"
> 
> #ifdef LDAP_OPT_SIZELIMIT
> #define NETSCAPE_LDAP_SDK
> #else
> #define UMICH_LDAP_SDK
> #endif
> 
> #ifdef NETSCAPE_LDAP_SDK
> #define GET_LDERROR(ld) ldap_get_lderrno((ld),0,0)
> #else
> #define GET_LDERROR(ld) (ld)->ld_errno
> #endif
> 
> /*
>  * A user found in the directory must be of this objectClass to be
>  * deemed a valid FTP user.
>  */
> #define FTP_OBJECTCLASS       "ftpUser"
> 
> #define FREE(x)                       { if ((x)) free((x)); (x)=0; }
> 
> void reply(int code, char *fmt, ...);
> void lreply(int code, char *fmt, ...);
> 
> /*
>  * It seems that global variables are coming in vogue again.
>  */
> static LDAP *ld=0;
> static struct timeval tv;
> static struct passwd pw;
> static char *dn=0;
> static char *cn=0;
> static char base64digest[29];
> static char *diruser=0;
> static char *dirpass=0;
> static char *dirhost="localhost";
> static int dirport=LDAP_PORT;
> static char *dirbase="o=osp.nl";
> 
> /* 
>  * The list of entry attributes that I am interested in. An ldap_search
>  * will return only these attributes.
>  */
> static char *ftpattrs[] = {
>       "objectClass",
>       "cn",
>       "gidNumber",
>       "uidNumber",
>       "homeDirectory",
>       "gecos",
>       0
> };
> 
> /*
>  * Something went wrong. Let's try to give a meaningful error. The error
>  * is written to the syslog, and to the FTP user (at the client side) as
>  * a 500 permanent error reply. The FTP client on the remote site will
>  * terminate the connection.
>  */
> static void *error(LDAP *ld,char *s)
> {
>       int err=0;
>       char *msg;
> 
>       if (ld) {
>               err=GET_LDERROR(ld);
>               msg=ldap_err2string(err);
>               syslog(LOG_ERR,"LDAP error: %s reports %s",s,msg);
>               reply(500,"LDAP error: %s reports %s",s,msg);
>       } else {
>               syslog(LOG_ERR,"General LDAP error: %s",s);
>               reply(500,"General LDAP error: %s",s);
>       }
> 
>       return 0;
> }
> 
> /*
>  * Initialise the connection to the directory server. First retrieve the
>  * necessary parameters from the ftpaccess file, the set up an LDAP
>  * connection.
>  */
> static int init_ldap() 
> {
>       struct aclmember *entry;
>       int i,len;
>       char *buf;
> 
>       entry=0;
>       if (getaclentry("ldap-host", &entry) && ARG0) dirhost=ARG0;
> 
>       entry=0;
>       if (getaclentry("ldap-port", &entry) && ARG0) dirport=atoi(ARG0);
> 
>       /*
>        * An LDAP bind dn can (and almost always will) contain spaces.
>        * Therefore, I construct the bind dn from the entire set of
>        * arguments specified on the "ldap-user" line.
>        */
>       entry=0;
>       if (getaclentry("ldap-user", &entry) && ARG0) {
> 
>               for (len=i=0; i<MAXARGS && ARG[i]; i++)
>                       len+=strlen(ARG[i]);
> 
>               buf=malloc(len+1);
>               *buf=0;
> 
>               for (i=0; i<MAXARGS && ARG[i]; i++) {
>                       if (i>0) strcat(buf," ");
>                       strcat(buf,ARG[i]);
>               }
> 
>               FREE(diruser);
>               diruser=buf;
>       }
> 
>       /*
>        * Currently, the password is specified in clear text in the
>        * ftpaccess file. I must do something about that.
>        */
>       entry=0;
>       if (getaclentry("ldap-password", &entry) && ARG0) dirpass=ARG0;
> 
>       entry=0;
>       if (getaclentry("ldap-base", &entry) && ARG0) dirbase=ARG0;
> 
>       /*
>        * syslog(LOG_INFO,"LDAP parameters:");
>        * syslog(LOG_INFO,"server   = %s",dirhost);
>        * syslog(LOG_INFO,"port     = %d",dirport);
>        * syslog(LOG_INFO,"base dn  = %s",dirbase);
>        * syslog(LOG_INFO,"bind dn  = %s",diruser?diruser:"(anonymous)");
>        * syslog(LOG_INFO,"password = %s",dirpass?"(withheld)":"(none)");
>        */
> 
>       /*
>        * Build a connection to the directory server.
>        */
>       if (!(ld=ldap_init(dirhost,dirport)))
>               return (int)error(0,"ldap_init()");
> 
>       /*
>        * Bind to the directory server as the specified user (bind dn)
>        * and with the specified password. If they are not specified in
>        * the ftpaccess file, we bind as the anonymous user, but it is
>        * then up to you to make sure that this user has enough rights
>        * to perform searches and compares in the directory. Especially
>        * the userPassword attribute is critical, since most
>        * directories protect this attribute from anonymous access by
>        * default.
>        */
>       if (ldap_simple_bind_s(ld,diruser,dirpass)!=LDAP_SUCCESS) 
>               return (int)error(ld,"ldap_bind");
> 
>       /*
>        * This struct timeval is used to limit the maximum time an LDAP
>        * search operation can take to 10 seconds.
>        */
>       tv.tv_sec=10;
>       tv.tv_usec=0;
> 
>       return 1;
> }
> 
> /*
>  * Check whether the user we found in the directory is of the correct
>  * objectClass. I could have fired off an ldap_compare_s(), but since we
>  * have the data in storage on the client, why not go through the data
>  * myself?
>  */
> static int check_ftpUser(LDAP *ld, LDAPMessage *entry)
> {
>       char **vals;
>       int i;
> 
>       vals=ldap_get_values(ld,entry,"objectClass");
> 
>       if (!vals) return 0;
> 
>       for (i=0; vals[i]; i++) 
>               if (strcmp(vals[i],FTP_OBJECTCLASS)==0) {
>                       ldap_value_free(vals);
>                       return 1;
>               }
> 
>       ldap_value_free(vals);
>       return 0;
> }
> 
> /* 
>  * Return an LDAP entry attribute of the user as a string.
>  */
> static char *attr_str(LDAP *ld, LDAPMessage *entry, char *attr, char *def)
> {
>       char **vals;
>       char *result;
> 
>       vals=ldap_get_values(ld,entry,attr);
> 
>       if (vals) {
>               result=strdup(vals[0]);
>               ldap_value_free(vals);
>       } else if (def)
>               result=strdup(def);
>       else
>               result=0;
> 
>       return result;
> }
> 
> /* 
>  * Return an LDAP entry attribute of the user as an integer.
>  */
> static int attr_num(LDAP *ld, LDAPMessage *entry, char *attr, int def)
> {
>       char **vals;
>       int result;
> 
>       vals=ldap_get_values(ld,entry,attr);
> 
>       if (vals) {
>               result=atoi(vals[0]);
>               ldap_value_free(vals);
>       } else
>               result=def;
> 
>       return result;
> }
> 
> /*
>  * Search the directory for the given user, and fill an industry
>  * standard "struct passwd" with the values found. The main of the WU
>  * FTP daemon has got no clue where the data came from, and will use it
>  * regardless. Returns 0 if the * user can not be found, or is invalid.
>  */
> struct passwd *getpwnam_ldap(char *name)
> {
>       int             n;
>       LDAPMessage     *result,*entry;
>       char            buf[80];
> 
>       /*
>        * Initialise the LDAP connection.
>        */
>       if (!ld) if (!init_ldap()) return 0;
> 
>       /*
>        * Build the LDAP search filter. Rather simple, but exactly what
>        * we need.
>        */
>       sprintf(buf,"(uid=%s)",name);
> 
>       /* 
>        * Fire off the search operation. Only retrieves the attributes
>        * I'm interested in.
>        */
>       n=ldap_search_st(ld,dirbase,LDAP_SCOPE_SUBTREE,buf,
>               ftpattrs,0,&tv,&result);
> 
>       /*
>        * Oh, oh, the user does not exist!
>        */
>       if (n==LDAP_NO_SUCH_OBJECT) return 0;
> 
>       /*
>        * "If we don't succeed, we run the risk of failure" -- D. Quale
>        */
>       if (n!=LDAP_SUCCESS) return error(ld,"search");
> 
>       /*
>        * Count how much entries we got back from the directory.
>        */
>       if ((n=ldap_count_entries(ld,result))==-1) return error(ld,"count");
> 
>       /*
>        * Hmm, we got back more than one! I'll use the first one but
>        * let's be nice and give a warning.
>        */
>       if (n>1) syslog(LOG_WARNING,"ldap_search returned %d entries for %s",
>                       n,buf);
> 
>       entry=ldap_first_entry(ld,result);
>                       
>       /*
>        * Retrieve the Distinguised Name (DN) and Common Name (CN) of
>        * the user. And log who tries to log on.
>        */
>       FREE(dn);
>       dn=ldap_get_dn(ld,entry);
>       FREE(cn);
>       cn=attr_str(ld,entry,"cn",0);
>       syslog(LOG_INFO,"%s recognised as %s (%s)",name,dn,cn?cn:"");
> 
>       /*
>        * Check whether this user is of the correct objectClass
>        * (ftpUser). If not, let's pretend he does not exist at all.
>        */
>       if (!check_ftpUser(ld,entry)) {
>               syslog(LOG_ERR,"%s is not of type (objectclass) %s",
>                       name,FTP_OBJECTCLASS);
>               return 0;
>       }
> 
>       /*
>        * Ok, I now fill the struct passwd with the fields from the
>        * directory entry.
>        */
>       FREE(pw.pw_name);
>       FREE(pw.pw_passwd);
>       FREE(pw.pw_gecos);
>       FREE(pw.pw_dir);
>       FREE(pw.pw_shell);
> 
>       pw.pw_name=strdup(name);
>       pw.pw_passwd=strdup("secret :-)");
>       pw.pw_gecos=attr_str(ld,entry,"gecos","");
>       pw.pw_dir=attr_str(ld,entry,"homeDirectory",0);
> 
>       /*
>        * This should not happen, but for those who have not configured
>        * the directory correctly... (mind you, schema checking *is* a
>        * good idea).
>        */
>       if (!pw.pw_dir) {
>               syslog(LOG_ERR,"%s has no homeDirectory attribute",name);
>               return 0;
>       }
> 
>       /*
>        * An empty shell is handled correctly by the core of WU FTPD.
>        */
>       pw.pw_shell=strdup("");
>       pw.pw_uid=attr_num(ld,entry,"uidNumber",-1);
> 
>       /*
>        * Again, this should not happen.
>        */
>       if (pw.pw_uid<0) {
>               syslog(LOG_ERR,"%s has no uidNumber attribute",name);
>               return 0;
>       }
> 
>       pw.pw_gid=attr_num(ld,entry,"gidNumber",-1);
> 
>       /*
>        * And again, this should not happen as well.
>        */
>       if (pw.pw_gid<0) {
>               syslog(LOG_ERR,"%s has no gidNumber attribute",name);
>               return 0;
>       }
> 
>       /*
>        * Ok, we're done. Bring out the dancing girls!
>        */
>       ldap_msgfree(result);
> 
>       return &pw;
> }
> 
> /*
>  * Close the LDAP connection. Quiz question: from where is this called?
>  */
> void close_ldap()
> {
>       if (!ld) return;
> 
>       ldap_unbind(ld);
>       ld=0;
> }
> 
> /*
>  * Calculate the MD5 hash of a password. This code has been adapted from
>  * the OpenLDAP passwd.c source in liblutil. Thanks guys!
>  */
> static char *md5_passwd(char *cred)
> {
>       lutil_MD5_CTX MD5context;
>       unsigned char MD5digest[16];
> 
>       lutil_MD5Init(&MD5context);
>       lutil_MD5Update(&MD5context,(const unsigned char *)cred,strlen(cred));
>       lutil_MD5Final(MD5digest, &MD5context);
> 
>       if (lutil_b64_ntop(MD5digest, sizeof(MD5digest), base64digest, 
>                                       sizeof(base64digest)) < 0)
>                       return 0;
> 
>       return base64digest;
> }
> 
> /*
>  * Calculate the SHA hash of a password. This code has been adapted from the
>  * OpenLDAP passwd.c source in liblutil. Thanks guys!
>  */
> static char *sha_passwd(char *cred)
> {
>       lutil_SHA1_CTX SHA1context;
>       unsigned char SHA1digest[20];
> 
>       lutil_SHA1Init(&SHA1context);
>       lutil_SHA1Update(&SHA1context,(const unsigned char *)cred,strlen(cred));
>       lutil_SHA1Final(SHA1digest, &SHA1context);
> 
>       if (lutil_b64_ntop(SHA1digest, sizeof(SHA1digest),
>                       base64digest, sizeof(base64digest)) < 0)
>               return 0;
> 
>       return base64digest;
> }
> 
> /* 
>  * This helper routine checkes whether its argument string happens to be
>  * the same as the content of the userPassword field of the user trying
>  * to log on. For obvious reasons, this is done with an ldap_compare(),
>  * so it is the server that does the comparison. Any hashing or crypting
>  * of the password has already been done by the caller.
>  */
> static int check_pass(char *s)
> {
>       int n=ldap_compare_s(ld,dn,"userPassword",s);
> 
>       if (n==LDAP_COMPARE_TRUE)       return 1;
>       if (n==LDAP_COMPARE_FALSE)      return 0;
> 
>       error(ld,"ldap_compare");
>       return 0;
> }
> 
> /*
>  * This routine is the core of the password checking routine. It takes
>  * the password supplied by the FTP user and tries to match it with the
>  * userPassword field of the user in the directory (through the
>  * check_pass helper). Most directories can hash the userPassword so
>  * that it is obscured from the directory administrator. This routine
>  * tries the most common hashing algorithms: SHA and MD5. If they do not
>  * match, a clear text match is tried.
>  */
> static int check_pass_ldap_horse(char *passwd)
> {
>       char *s;
>       char buf[80];
> 
>       /*
>        * This should not happen.
>        */
>       if (!ld || !dn) return 0;
> 
>       /*
>        * Hash the supplied password with SHA and compare it.
>        */
>       s=sha_passwd(passwd);
> 
>       if (s) {
>               sprintf(buf,"{SHA}%s",s);
>               if (check_pass(buf)) return 1;
>       }
> 
>       /*
>        * Hash the supplied password with MD5 and compare it.
>        */
>       s=md5_passwd(passwd);
> 
>       if (s) {
>               sprintf(buf,"{MD5}%s",s);
>               if (check_pass(buf)) return 1;
>       }
> 
>       /*
>        * If all else fails, try a clear text match, but give a warning
>        * if it matches. You better configure your directory for SHA
>        * hashing of the userPassword field.
>        */
>       if (check_pass(passwd)) {
>               syslog(LOG_WARNING,"%s uses a clear text password",dn);
>               return 1;
>       }
> 
>       /*
>        * No match!
>        */
>       return 0;
> }
> 
> /*
>  * The main password checking routine. Uses the horse function to
>  * perform the actual check, and give a nice welcome to the user if the
>  * supplied password matches.
>  */
> int check_pass_ldap(char *passwd)
> {
>       char *s;
> 
>       if (check_pass_ldap_horse(passwd)) {
>               lreply(230,"Welcome %s",cn?cn:dn);
>               return 1;
>       }
> 
>       return 0;
> }
> 
> #endif /* USE_LDAP */
> 
diff -N -r wu-ftpd-2.5.0/src/makefiles/Makefile.lnx
wu-ftpd-2.5.0-ldap/src/makefiles/Makefile.lnx
27a28,33
>             if [ "${USE_LDAP}" = "1" ]; then \
>               echo "-DUSE_LDAP"; \
>                 if [ "${LDAP_INCLUDE}" != "" ]; then \
>                   echo "-I ${LDAP_INCLUDE}"; \
>                 fi; \
>             fi; \
48a55,60
>             fi; \
>             if [ "${USE_LDAP}" = "1" ]; then \
>                 if [ "${LDAP_LIBS}" != "" ]; then \
>                   echo "-L ${LDAP_LIBS}"; \
>                 fi; \
>               echo "-lldap -llber -llutil"; \
diff -N -r wu-ftpd-2.5.0/src/makefiles/common
wu-ftpd-2.5.0-ldap/src/makefiles/common
4c4
<          sigfix.c routevector.c restrict.c
---
>          sigfix.c routevector.c restrict.c ldapftp.c
7c7
<          sigfix.o routevector.o restrict.o
---
>          sigfix.o routevector.o restrict.o ldapftp.o
=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT=CUT
 
-- 
Meningitis is one of the scariest diseases I know. If not diagnosed
correctly and treated quickly it can kill anyone, anywhere, anytime  
in a matter of hours! Don't let meningitis surprise you!

Read up on this disease at http://www.musa.org.