/***********************************************************************************************************************
*
* Elasticsearch IMAP/Pop3 E-Mail Importer
* ==========================================
*
* Copyright (C) 2014 by Hendrik Saly (http://saly.de) and others.
*
* Contains (partially) copied code from Jörg Prante's Elasticsearch JDBC river (https://github.com/jprante/elasticsearch-river-jdbc)
*
***********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
***********************************************************************************************************************
*
* $Id:$
*
**********************************************************************************************************************/
package de.saly.elasticsearch.importer.imap.ldap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.SearchResult;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import com.google.common.collect.Lists;
/**
* Login data provider connecting to an ldap directory, reading all available
* users. For Dovecot, a master user account can be supplied who can access
* all users' mailboxes, even if their passwords are encrypted.
*/
public class LdapLoginSource implements ILoginSource, Runnable {
private static final String DEF_USER_NAME_FIELD = "dn";
private static final String DEF_USER_PW_FIELD = "userPassword";
private static final String DEF_REFRESH_INT = "60";
private static final String MASTER_SEP = "*";
private final String fNameField;
private final String fPasswordField;
private final String fLdapFilter;
private final long fRefreshInt;
// TODO: filter configuration?
private final List<String> fUserNames, fUserPasswords;
private final String fMasterUser, fMasterPassword;
private final ILdapConnector fConnector;
private final ESLogger fLogger;
private final Object fLock;
private volatile boolean fInitialized;
private volatile boolean fActive;
public LdapLoginSource(Map<String, Object> settings) {
this(settings, null, null);
}
public LdapLoginSource(Map<String, Object> settings, String masterUser, String masterPassword) {
fUserNames = new ArrayList<>();
fUserPasswords = new ArrayList<>();
fMasterUser = masterUser;
fMasterPassword = masterPassword;
String url = XContentMapValues.nodeStringValue(settings.get("ldap_url"), null);
String base = XContentMapValues.nodeStringValue(settings.get("ldap_base"), null);
String user = XContentMapValues.nodeStringValue(settings.get("ldap_user"), null);
String password = XContentMapValues.nodeStringValue(settings.get("ldap_password"), null);
fConnector = new SimpleLdapConnector(url, base, user, password, true);
fNameField = XContentMapValues.nodeStringValue(settings.get("ldap_name_field"), DEF_USER_NAME_FIELD);
fPasswordField = XContentMapValues.nodeStringValue(settings.get("ldap_password_field"), DEF_USER_PW_FIELD);
fLdapFilter = fNameField + "=*";
fLogger = ESLoggerFactory.getLogger(LdapLoginSource.class.getName());
fLock = new Object();
fInitialized = false;
//start refreshing thread once initialized; interval in minutes
String refreshParam = XContentMapValues.nodeStringValue(settings.get("ldap_refresh_interval"), DEF_REFRESH_INT);
fRefreshInt = Long.parseLong(refreshParam) * 60000L;
if(fRefreshInt > 0) {
//TODO: actually stop refreshing thread somehow
fActive = true;
Thread t = new Thread(this);
t.setDaemon(true);
t.start();
}
}
@SuppressWarnings("rawtypes")
private void read() throws Exception {
Exception ex = null;
synchronized (fLock) {
//backup old data
List<String> nameBackup = Lists.newArrayList(fUserNames);
List<String> passBackup = Lists.newArrayList(fUserPasswords);
try {
//clear old data
fUserNames.clear();
fUserPasswords.clear();
// start reading from LDAP
fConnector.connect();
NamingEnumeration data = fConnector.query("", fLdapFilter);
if (data != null) {
// extract usernames and passwords
readData(data);
}
}
catch(Exception e)
{
//use backup
fUserNames.clear();
fUserPasswords.clear();
fUserNames.addAll(nameBackup);
fUserPasswords.addAll(passBackup);
ex = e;
}
finally
{
// close connection
fConnector.disconnect();
}
}
if(ex != null) {
throw ex;
}
fInitialized = true;
}
@SuppressWarnings("rawtypes")
private void readData(NamingEnumeration ldapContents) throws Exception {
SearchResult result = null;
NamingEnumeration<? extends Attribute> atts = null;
Attribute a = null;
while (ldapContents.hasMore()) {
String name = null;
String password = null;
result = (SearchResult) ldapContents.next();
// comes in format "uid=name"
// in a subtree search, it's "uid=name,ou=..."
name = result.getName();
if(name.indexOf(',') > 0) {
name = name.split(",")[0];
}
name = name.split("=")[1];
// append master user if configured
if (fMasterUser != null) {
name += MASTER_SEP + fMasterUser;
}
// try password extraction
atts = result.getAttributes().getAll();
while (atts.hasMore()) {
a = atts.next();
if (fPasswordField.equals(a.getID()) && a.get() != null) {
Object val = a.get();
if (val instanceof byte[]) {
// hashed passwords
// TODO: usable at all?
password = new String((byte[]) val);
} else {
// clear text passwords
password = val.toString();
}
break;
}
}
// master password insertion
if (fMasterPassword != null) {
password = fMasterPassword;
}
// add name and password
fUserNames.add(name);
fUserPasswords.add(password);
}
}
@Override
public String getName() {
return "LDAP login source";
}
@Override
public List<String> getUserNames() {
if (!fInitialized) {
try {
read();
} catch (Exception e) {
fLogger.error("Failed to get usernames", e);
}
}
return fUserNames;
}
@Override
public List<String> getUserPasswords() {
if (!fInitialized) {
try {
read();
} catch (Exception e) {
e.printStackTrace();
fLogger.error("Failed to get passwords", e);
}
}
return fUserPasswords;
}
public void deactivate() {
fActive = false;
}
@Override
public void run() {
//refresh loop
while(fActive)
{
try {
read();
} catch (Exception e) {
fLogger.error("Failed to refresh", e);
}
try {
Thread.sleep(fRefreshInt);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
}
}