Apache FtpServer used to be bundled with an LDAP User Manager for authentication, but it was deleted from the repository in this commit in 2008.
Here is an alternative implementation:
LdapUserManager.java
package org.adrianwalker.ftpserver.usermanager.ldap;
import static java.lang.String.format;
import static org.apache.directory.ldap.client.api.search.FilterBuilder.and;
import static org.apache.directory.ldap.client.api.search.FilterBuilder.contains;
import static org.apache.directory.ldap.client.api.search.FilterBuilder.present;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.ldap.client.api.search.FilterBuilder;
import org.apache.directory.ldap.client.template.EntryMapper;
import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
import org.apache.directory.ldap.client.template.exception.PasswordException;
import org.apache.ftpserver.ftplet.Authentication;
import org.apache.ftpserver.ftplet.AuthenticationFailedException;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.User;
import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication;
import org.apache.ftpserver.usermanager.impl.AbstractUserManager;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public final class LdapUserManager extends AbstractUserManager {
private static final Logger LOGGER = LoggerFactory.getLogger(LdapUserManager.class);
private static final String ATTR_OBJECT_CLASS = "objectClass";
private static final String ATTR_UID = "uid";
private static final String ATTR_CN = "cn";
private static final String ATTR_SN = "sn";
private static final String ATTR_USER_PASSWORD = "userPassword";
private static final String ATTR_UNIX_FILE_PATH = "unixFilePath";
private static final String ATTR_PWD_ATTRBUTE = "pwdAttribute";
private static final String ATTR_PWD_MAX_IDLE = "pwdMaxIdle";
private static final String ATTR_PWD_LOCKOUT = "pwdLockout";
private static final String OBJECT_CLASS_INET_ORG_PERSON = "inetOrgPerson";
private static final String OBJECT_CLASS_EXTENSIBLE_OBJECT = "extensibleObject";
private final LdapConnectionTemplate ldapConnectionTemplate;
private final String userBaseDn;
public LdapUserManager(
final LdapConnectionTemplate ldapConnectionTemplate,
final String userBaseDn) {
this.ldapConnectionTemplate = ldapConnectionTemplate;
this.userBaseDn = userBaseDn;
}
@Override
public User getUserByName(final String name) throws FtpException {
LOGGER.debug("name = {}", name);
if (null == name) {
throw new IllegalArgumentException("name is null");
}
Dn dn = ldapConnectionTemplate.newDn(format("%s=%s,%s", ATTR_UID, name, userBaseDn));
return ldapConnectionTemplate.lookup(dn, entry -> createUser(entry));
}
@Override
public String[] getAllUserNames() throws FtpException {
Dn dn = ldapConnectionTemplate.newDn(userBaseDn);
FilterBuilder filter = and(
present(ATTR_UID),
contains(ATTR_OBJECT_CLASS, OBJECT_CLASS_INET_ORG_PERSON));
EntryMapper<String> mapper = entry -> toString(entry.get(ATTR_UID));
List<String> userNames = ldapConnectionTemplate.search(dn, filter, SearchScope.ONELEVEL, mapper);
LOGGER.debug("userNames = {}", userNames);
return userNames.toArray(new String[userNames.size()]);
}
@Override
public void delete(final String name) throws FtpException {
LOGGER.debug("name = {}", name);
if (null == name) {
throw new IllegalArgumentException("name is null");
}
Dn dn = ldapConnectionTemplate.newDn(format("%s=%s,%s", ATTR_UID, name, userBaseDn));
ldapConnectionTemplate.delete(dn);
}
@Override
public void save(final User user) throws FtpException {
LOGGER.debug("user = {}", user);
if (null == user) {
throw new IllegalArgumentException("user is null");
}
Dn dn = ldapConnectionTemplate.newDn(format("%s=%s,%s", ATTR_UID, user.getName(), userBaseDn));
String[] objectClasses = {
OBJECT_CLASS_INET_ORG_PERSON, OBJECT_CLASS_EXTENSIBLE_OBJECT
};
Attribute[] attributes = {
ldapConnectionTemplate.newAttribute(ATTR_OBJECT_CLASS, objectClasses),
ldapConnectionTemplate.newAttribute(ATTR_CN, user.getName()),
ldapConnectionTemplate.newAttribute(ATTR_SN, user.getName()),
ldapConnectionTemplate.newAttribute(ATTR_USER_PASSWORD, user.getPassword()),
ldapConnectionTemplate.newAttribute(ATTR_PWD_ATTRBUTE, ATTR_USER_PASSWORD),
ldapConnectionTemplate.newAttribute(ATTR_UNIX_FILE_PATH, user.getHomeDirectory()),
ldapConnectionTemplate.newAttribute(ATTR_PWD_MAX_IDLE, toString(user.getMaxIdleTime())),
ldapConnectionTemplate.newAttribute(ATTR_PWD_LOCKOUT, toString(!user.getEnabled()))
};
ldapConnectionTemplate.add(dn, attributes);
}
@Override
public boolean doesExist(final String name) throws FtpException {
LOGGER.debug("name = {}", name);
if (null == name) {
throw new IllegalArgumentException("name is null");
}
return null != getUserByName(name);
}
@Override
public User authenticate(final Authentication auth) throws AuthenticationFailedException {
LOGGER.debug("auth = {}", auth);
if (null == auth) {
throw new IllegalArgumentException("auth is null");
}
boolean isUsernamePasswordAuth = auth instanceof UsernamePasswordAuthentication;
if (!isUsernamePasswordAuth) {
throw new AuthenticationFailedException();
}
UsernamePasswordAuthentication usernamePasswordAuth = (UsernamePasswordAuthentication) auth;
String username = usernamePasswordAuth.getUsername();
String password = usernamePasswordAuth.getPassword();
Dn dn = ldapConnectionTemplate.newDn(format("%s=%s,%s", ATTR_UID, username, userBaseDn));
try {
ldapConnectionTemplate.authenticate(dn, password.toCharArray());
} catch (final PasswordException pe) {
LOGGER.error(pe.getMessage(), pe);
throw new AuthenticationFailedException(pe);
}
try {
return getUserByName(username);
} catch (final FtpException fe) {
LOGGER.error(fe.getMessage(), fe);
throw new AuthenticationFailedException(fe);
}
}
private User createUser(final Entry entry) throws LdapInvalidAttributeValueException {
BaseUser user = new BaseUser();
user.setName(toString(entry.get(ATTR_UID)));
user.setHomeDirectory(toString(entry.get(ATTR_UNIX_FILE_PATH)));
user.setMaxIdleTime(toInt(entry.get(ATTR_PWD_MAX_IDLE)));
user.setEnabled(!toBoolean(entry.get(ATTR_PWD_LOCKOUT)));
return user;
}
private boolean toBoolean(final Attribute attribute) throws LdapInvalidAttributeValueException {
return Boolean.parseBoolean(toString(attribute));
}
private int toInt(final Attribute attribute) throws LdapInvalidAttributeValueException {
return Integer.parseInt(toString(attribute));
}
private String toString(final Attribute attribute) throws LdapInvalidAttributeValueException {
return attribute.getString();
}
private String toString(final int value) {
return String.valueOf(value);
}
private String toString(final boolean value) {
return String.valueOf(value);
}
}
An example LDAP entry for use with Apache Directory Server should look something like this:
testuser.ldif
version: 1 dn: uid=testuser,ou=users,ou=system objectClass: extensibleObject objectClass: organizationalPerson objectClass: person objectClass: inetOrgPerson objectClass: top cn: testuser sn: testuser pwdAttribute: userPassword pwdLockout: false pwdMaxIdle: 1800 uid: testuser unixFilePath: /testuser userPassword:: e1NTSEF9QUJhbUQ2eHZEbk91czBFVDhzWmtpdk9MWXdSYWRzU3B0UnhlK1E9P Q==
Example usage when used with an embedded FTP server:
private static void exampleUsage() throws FtpException {
LdapConnectionConfig config = new LdapConnectionConfig();
config.setLdapHost("localhost");
config.setLdapPort(10389);
config.setName("uid=admin,ou=system");
config.setCredentials("secret");
GenericObjectPool.Config poolConfig = new GenericObjectPool.Config();
poolConfig.maxActive = 200;
poolConfig.maxIdle = 20;
DefaultLdapConnectionFactory ldapConnectionFactory = new DefaultLdapConnectionFactory(config);
ldapConnectionFactory.setTimeOut(1000 * 60 * 3);
ValidatingPoolableLdapConnectionFactory poolableLdapConnectionFactory
= new ValidatingPoolableLdapConnectionFactory(ldapConnectionFactory);
LdapConnectionPool ldapPool = new LdapConnectionPool(poolableLdapConnectionFactory, poolConfig);
LdapConnectionTemplate ldapConnectionTemplate = new LdapConnectionTemplate(ldapPool);
ListenerFactory listenerFactory = new ListenerFactory();
listenerFactory.setPort(8021);
FtpServerFactory serverFactory = new FtpServerFactory();
serverFactory.addListener("default", listenerFactory.createListener());
serverFactory.setUserManager(new LdapUserManager(ldapConnectionTemplate, "ou=users,ou=system"));
FtpServer server = serverFactory.createServer();
server.start();
}
Source Code
- Code available in GitHub - ftpserver-usermanager
Build and Test
The project is a standard Maven project which can be built with:
mvn clean install