1
/* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2
* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
4
* Copyright (C) 2010 Eric Day
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; version 2 of the License.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
/* This is needed for simple auth, we're not ready for SASL yet. */
23
#define LDAP_DEPRECATED 1
31
#include "drizzled/plugin/authentication.h"
32
#include "drizzled/identifier.h"
33
#include "drizzled/util/convert.h"
34
#include "drizzled/algorithm/sha1.h"
36
#include <drizzled/module/option_map.h>
37
#include <boost/program_options.hpp>
39
namespace po= boost::program_options;
41
using namespace drizzled;
47
const std::string DEFAULT_URI= "ldap://127.0.0.1/";
49
std::string bind_password;
51
std::string password_attribute;
52
std::string DEFAULT_PASSWORD_ATTRIBUTE= "userPassword";
53
std::string mysql_password_attribute;
54
const std::string DEFAULT_MYSQL_PASSWORD_ATTRIBUTE= "mysqlUserPassword";
55
static const int DEFAULT_CACHE_TIMEOUT= 600;
56
typedef constrained_check<int, DEFAULT_CACHE_TIMEOUT, 0, 2147483647> cachetimeout_constraint;
57
static cachetimeout_constraint cache_timeout= 0;
60
class AuthLDAP: public plugin::Authentication
64
AuthLDAP(string name_arg);
68
* Initialize the LDAP connection.
70
* @return True on success, false otherwise.
72
bool initialize(void);
75
* Connect to the LDAP server.
77
* @return True on success, false otherwise.
82
* Retrieve the last error encountered in the class.
84
string& getError(void);
95
typedef std::pair<PasswordType, std::string> PasswordEntry;
96
typedef std::pair<std::string, PasswordEntry> UserEntry;
97
typedef std::map<std::string, PasswordEntry> UserCache;
100
* Base class method to check authentication for a user.
102
bool authenticate(const identifier::User &sctx, const string &password);
105
* Lookup a user in LDAP.
107
* @param[in] Username to lookup.
109
void lookupUser(const string& user);
112
* Verify the local and remote scrambled password match using the MySQL
115
* @param[in] password Plain text password that is stored locally.
116
* @param[in] scramble_bytes The random bytes that the server sent to the
117
* client for scrambling the password.
118
* @param[in] scrambled_password The result of the client scrambling the
120
* @return True if the password matched, false if not.
122
bool verifyMySQLHash(const PasswordEntry &password,
123
const string &scramble_bytes,
124
const string &scrambled_password);
126
time_t next_cache_expiration;
130
pthread_rwlock_t lock;
133
AuthLDAP::AuthLDAP(string name_arg):
134
plugin::Authentication(name_arg),
135
next_cache_expiration(),
142
AuthLDAP::~AuthLDAP()
144
pthread_rwlock_destroy(&lock);
149
bool AuthLDAP::initialize(void)
151
int return_code= pthread_rwlock_init(&lock, NULL);
152
if (return_code != 0)
154
error= "pthread_rwlock_init failed";
161
bool AuthLDAP::connect(void)
163
int return_code= ldap_initialize(&ldap, (char *)uri.c_str());
164
if (return_code != LDAP_SUCCESS)
166
error= "ldap_initialize failed: ";
167
error+= ldap_err2string(return_code);
172
return_code= ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
173
if (return_code != LDAP_SUCCESS)
177
error= "ldap_set_option failed: ";
178
error+= ldap_err2string(return_code);
182
if (not bind_dn.empty())
184
return_code= ldap_simple_bind_s(ldap, (char *)bind_dn.c_str(), (char *)bind_password.c_str());
185
if (return_code != LDAP_SUCCESS)
189
error= "ldap_simple_bind_s failed: ";
190
error+= ldap_err2string(return_code);
198
string& AuthLDAP::getError(void)
203
bool AuthLDAP::authenticate(const identifier::User &sctx, const string &password)
205
/* See if cache should be emptied. */
206
if (cache_timeout > 0)
208
struct timeval current_time;
209
gettimeofday(¤t_time, NULL);
210
if (current_time.tv_sec > next_cache_expiration)
212
pthread_rwlock_wrlock(&lock);
213
/* Make sure another thread didn't already clear it. */
214
if (current_time.tv_sec > next_cache_expiration)
217
next_cache_expiration= current_time.tv_sec + cache_timeout;
219
pthread_rwlock_unlock(&lock);
223
pthread_rwlock_rdlock(&lock);
225
AuthLDAP::UserCache::const_iterator user= users.find(sctx.username());
226
if (user == users.end())
228
pthread_rwlock_unlock(&lock);
230
pthread_rwlock_wrlock(&lock);
232
/* Make sure the user was not added while we unlocked. */
233
user= users.find(sctx.username());
234
if (user == users.end())
235
lookupUser(sctx.username());
237
pthread_rwlock_unlock(&lock);
239
pthread_rwlock_rdlock(&lock);
241
/* Get user again because map may have changed while unlocked. */
242
user= users.find(sctx.username());
243
if (user == users.end())
245
pthread_rwlock_unlock(&lock);
250
if (user->second.first == NOT_FOUND)
252
pthread_rwlock_unlock(&lock);
256
if (sctx.getPasswordType() == identifier::User::MYSQL_HASH)
258
bool allow= verifyMySQLHash(user->second, sctx.getPasswordContext(), password);
259
pthread_rwlock_unlock(&lock);
263
if (user->second.first == PLAIN_TEXT && password == user->second.second)
265
pthread_rwlock_unlock(&lock);
269
pthread_rwlock_unlock(&lock);
273
void AuthLDAP::lookupUser(const string& user)
275
string filter("(cn=" + user + ")");
276
const char *attributes[3]=
278
(char *)password_attribute.c_str(),
279
(char *)mysql_password_attribute.c_str(),
283
bool try_reconnect= true;
291
errmsg_printf(error::ERROR, _("Reconnect failed: %s\n"),
297
int return_code= ldap_search_ext_s(ldap,
298
(char *)base_dn.c_str(),
301
const_cast<char **>(attributes),
308
if (return_code != LDAP_SUCCESS)
310
errmsg_printf(error::ERROR, _("ldap_search_ext_s failed: %s\n"),
311
ldap_err2string(return_code));
313
/* Only try one reconnect per request. */
316
try_reconnect= false;
328
LDAPMessage *entry= ldap_first_entry(ldap, result);
329
AuthLDAP::PasswordEntry new_password;
331
new_password= AuthLDAP::PasswordEntry(NOT_FOUND, "");
334
char **values= ldap_get_values(ldap, entry, (char *)mysql_password_attribute.c_str());
337
values= ldap_get_values(ldap, entry, (char *)password_attribute.c_str());
339
new_password= AuthLDAP::PasswordEntry(NOT_FOUND, "");
342
new_password= AuthLDAP::PasswordEntry(PLAIN_TEXT, values[0]);
343
ldap_value_free(values);
348
new_password= AuthLDAP::PasswordEntry(MYSQL_HASH, values[0]);
349
ldap_value_free(values);
353
users.insert(AuthLDAP::UserEntry(user, new_password));
356
bool AuthLDAP::verifyMySQLHash(const PasswordEntry &password,
357
const string &scramble_bytes,
358
const string &scrambled_password)
360
if (scramble_bytes.size() != SHA1_DIGEST_LENGTH ||
361
scrambled_password.size() != SHA1_DIGEST_LENGTH)
367
uint8_t local_scrambled_password[SHA1_DIGEST_LENGTH];
368
uint8_t temp_hash[SHA1_DIGEST_LENGTH];
369
uint8_t scrambled_password_check[SHA1_DIGEST_LENGTH];
371
if (password.first == MYSQL_HASH)
373
/* Get the double-hashed password from the given hex string. */
374
drizzled_hex_to_string(reinterpret_cast<char*>(local_scrambled_password),
375
password.second.c_str(), SHA1_DIGEST_LENGTH * 2);
379
/* Generate the double SHA1 hash for the password stored locally first. */
381
SHA1Update(&ctx, reinterpret_cast<const uint8_t *>(password.second.c_str()),
382
password.second.size());
383
SHA1Final(temp_hash, &ctx);
386
SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
387
SHA1Final(local_scrambled_password, &ctx);
390
/* Hash the scramble that was sent to client with the local password. */
392
SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()),
394
SHA1Update(&ctx, local_scrambled_password, SHA1_DIGEST_LENGTH);
395
SHA1Final(temp_hash, &ctx);
397
/* Next, XOR the result with what the client sent to get the original
398
single-hashed password. */
399
for (int x= 0; x < SHA1_DIGEST_LENGTH; x++)
400
temp_hash[x]= temp_hash[x] ^ scrambled_password[x];
402
/* Hash this result once more to get the double-hashed password again. */
404
SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
405
SHA1Final(scrambled_password_check, &ctx);
407
/* These should match for a successful auth. */
408
return memcmp(local_scrambled_password, scrambled_password_check, SHA1_DIGEST_LENGTH) == 0;
411
static int init(module::Context &context)
413
AuthLDAP *auth_ldap= new AuthLDAP("auth_ldap");
414
if (! auth_ldap->initialize())
416
errmsg_printf(error::ERROR, _("Could not load auth ldap: %s\n"),
417
auth_ldap->getError().c_str());
422
context.registerVariable(new sys_var_const_string_val("uri", uri));
423
context.registerVariable(new sys_var_const_string_val("bind-dn", bind_dn));
424
context.registerVariable(new sys_var_const_string_val("bind-password", bind_password));
425
context.registerVariable(new sys_var_const_string_val("base-dn", base_dn));
426
context.registerVariable(new sys_var_const_string_val("password-attribute",password_attribute));
427
context.registerVariable(new sys_var_const_string_val("mysql-password-attribute", mysql_password_attribute));
428
context.registerVariable(new sys_var_constrained_value_readonly<int>("cache-timeout", cache_timeout));
430
context.add(auth_ldap);
434
static void init_options(drizzled::module::option_context &context)
436
context("uri", po::value<string>(&uri)->default_value(DEFAULT_URI),
437
N_("URI of the LDAP server to contact"));
438
context("bind-db", po::value<string>(&bind_dn)->default_value(""),
439
N_("DN to use when binding to the LDAP server"));
440
context("bind-password", po::value<string>(&bind_password)->default_value(""),
441
N_("Password to use when binding the DN"));
442
context("base-dn", po::value<string>(&base_dn)->default_value(""),
443
N_("DN to use when searching"));
444
context("password-attribute", po::value<string>(&password_attribute)->default_value(DEFAULT_PASSWORD_ATTRIBUTE),
445
N_("Attribute in LDAP with plain text password"));
446
context("mysql-password-attribute", po::value<string>(&mysql_password_attribute)->default_value(DEFAULT_MYSQL_PASSWORD_ATTRIBUTE),
447
N_("Attribute in LDAP with MySQL hashed password"));
448
context("cache-timeout", po::value<cachetimeout_constraint>(&cache_timeout)->default_value(DEFAULT_CACHE_TIMEOUT),
449
N_("How often to empty the users cache, 0 to disable"));
452
} /* namespace auth_ldap */
454
DRIZZLE_PLUGIN(auth_ldap::init, NULL, auth_ldap::init_options);