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/security_context.h"
33
#include "drizzled/util/convert.h"
34
#include "drizzled/algorithm/sha1.h"
37
using namespace drizzled;
42
static char *uri= NULL;
43
static const char DEFAULT_URI[]= "ldap://127.0.0.1/";
44
static char *bind_dn= NULL;
45
static char *bind_password= NULL;
46
static char *base_dn= NULL;
47
static char *password_attribute= NULL;
48
static const char DEFAULT_PASSWORD_ATTRIBUTE[]= "userPassword";
49
static char *mysql_password_attribute= NULL;
50
static const char DEFAULT_MYSQL_PASSWORD_ATTRIBUTE[]= "mysqlUserPassword";
51
static int cache_timeout= 0;
52
static const int DEFAULT_CACHE_TIMEOUT= 600;
54
class AuthLDAP: public plugin::Authentication
58
AuthLDAP(string name_arg);
62
* Initialize the LDAP connection.
64
* @return True on success, false otherwise.
66
bool initialize(void);
69
* Connect to the LDAP server.
71
* @return True on success, false otherwise.
76
* Retrieve the last error encountered in the class.
78
string& getError(void);
89
typedef pair<PasswordType, string> PasswordEntry;
90
typedef pair<string, PasswordEntry> UserEntry;
91
typedef map<string, PasswordEntry> UserCache;
94
* Base class method to check authentication for a user.
96
bool authenticate(const SecurityContext &sctx, const string &password);
99
* Lookup a user in LDAP.
101
* @param[in] Username to lookup.
103
void lookupUser(const string& user);
106
* Verify the local and remote scrambled password match using the MySQL
109
* @param[in] password Plain text password that is stored locally.
110
* @param[in] scramble_bytes The random bytes that the server sent to the
111
* client for scrambling the password.
112
* @param[in] scrambled_password The result of the client scrambling the
114
* @return True if the password matched, false if not.
116
bool verifyMySQLHash(const PasswordEntry &password,
117
const string &scramble_bytes,
118
const string &scrambled_password);
120
time_t next_cache_expiration;
124
pthread_rwlock_t lock;
127
AuthLDAP::AuthLDAP(string name_arg):
128
plugin::Authentication(name_arg),
129
next_cache_expiration(),
136
AuthLDAP::~AuthLDAP()
138
pthread_rwlock_destroy(&lock);
143
bool AuthLDAP::initialize(void)
145
int return_code= pthread_rwlock_init(&lock, NULL);
146
if (return_code != 0)
148
error= "pthread_rwlock_init failed";
155
bool AuthLDAP::connect(void)
157
int return_code= ldap_initialize(&ldap, uri);
158
if (return_code != LDAP_SUCCESS)
160
error= "ldap_initialize failed: ";
161
error+= ldap_err2string(return_code);
166
return_code= ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
167
if (return_code != LDAP_SUCCESS)
171
error= "ldap_set_option failed: ";
172
error+= ldap_err2string(return_code);
178
return_code= ldap_simple_bind_s(ldap, bind_dn, bind_password);
179
if (return_code != LDAP_SUCCESS)
183
error= "ldap_simple_bind_s failed: ";
184
error+= ldap_err2string(return_code);
192
string& AuthLDAP::getError(void)
197
bool AuthLDAP::authenticate(const SecurityContext &sctx, const string &password)
199
/* See if cache should be emptied. */
200
if (cache_timeout > 0)
202
struct timeval current_time;
203
gettimeofday(¤t_time, NULL);
204
if (current_time.tv_sec > next_cache_expiration)
206
pthread_rwlock_wrlock(&lock);
207
/* Make sure another thread didn't already clear it. */
208
if (current_time.tv_sec > next_cache_expiration)
211
next_cache_expiration= current_time.tv_sec + cache_timeout;
213
pthread_rwlock_unlock(&lock);
217
pthread_rwlock_rdlock(&lock);
219
AuthLDAP::UserCache::const_iterator user= users.find(sctx.getUser());
220
if (user == users.end())
222
pthread_rwlock_unlock(&lock);
224
pthread_rwlock_wrlock(&lock);
226
/* Make sure the user was not added while we unlocked. */
227
user= users.find(sctx.getUser());
228
if (user == users.end())
229
lookupUser(sctx.getUser());
231
pthread_rwlock_unlock(&lock);
233
pthread_rwlock_rdlock(&lock);
235
/* Get user again because map may have changed while unlocked. */
236
user= users.find(sctx.getUser());
237
if (user == users.end())
239
pthread_rwlock_unlock(&lock);
244
if (user->second.first == NOT_FOUND)
246
pthread_rwlock_unlock(&lock);
250
if (sctx.getPasswordType() == SecurityContext::MYSQL_HASH)
252
bool allow= verifyMySQLHash(user->second, sctx.getPasswordContext(), password);
253
pthread_rwlock_unlock(&lock);
257
if (user->second.first == PLAIN_TEXT && password == user->second.second)
259
pthread_rwlock_unlock(&lock);
263
pthread_rwlock_unlock(&lock);
267
void AuthLDAP::lookupUser(const string& user)
269
string filter("(cn=" + user + ")");
270
const char *attributes[3]=
273
mysql_password_attribute,
277
bool try_reconnect= true;
285
errmsg_printf(ERRMSG_LVL_ERROR, _("Reconnect failed: %s\n"),
291
int return_code= ldap_search_ext_s(ldap,
295
const_cast<char **>(attributes),
302
if (return_code != LDAP_SUCCESS)
304
errmsg_printf(ERRMSG_LVL_ERROR, _("ldap_search_ext_s failed: %s\n"),
305
ldap_err2string(return_code));
307
/* Only try one reconnect per request. */
310
try_reconnect= false;
322
LDAPMessage *entry= ldap_first_entry(ldap, result);
323
AuthLDAP::PasswordEntry new_password;
325
new_password= AuthLDAP::PasswordEntry(NOT_FOUND, "");
328
char **values= ldap_get_values(ldap, entry, mysql_password_attribute);
331
values= ldap_get_values(ldap, entry, password_attribute);
333
new_password= AuthLDAP::PasswordEntry(NOT_FOUND, "");
336
new_password= AuthLDAP::PasswordEntry(PLAIN_TEXT, values[0]);
337
ldap_value_free(values);
342
new_password= AuthLDAP::PasswordEntry(MYSQL_HASH, values[0]);
343
ldap_value_free(values);
347
users.insert(AuthLDAP::UserEntry(user, new_password));
350
bool AuthLDAP::verifyMySQLHash(const PasswordEntry &password,
351
const string &scramble_bytes,
352
const string &scrambled_password)
354
if (scramble_bytes.size() != SHA1_DIGEST_LENGTH ||
355
scrambled_password.size() != SHA1_DIGEST_LENGTH)
361
uint8_t local_scrambled_password[SHA1_DIGEST_LENGTH];
362
uint8_t temp_hash[SHA1_DIGEST_LENGTH];
363
uint8_t scrambled_password_check[SHA1_DIGEST_LENGTH];
365
if (password.first == MYSQL_HASH)
367
/* Get the double-hashed password from the given hex string. */
368
drizzled_hex_to_string(reinterpret_cast<char*>(local_scrambled_password),
369
password.second.c_str(), SHA1_DIGEST_LENGTH * 2);
373
/* Generate the double SHA1 hash for the password stored locally first. */
375
SHA1Update(&ctx, reinterpret_cast<const uint8_t *>(password.second.c_str()),
376
password.second.size());
377
SHA1Final(temp_hash, &ctx);
380
SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
381
SHA1Final(local_scrambled_password, &ctx);
384
/* Hash the scramble that was sent to client with the local password. */
386
SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()),
388
SHA1Update(&ctx, local_scrambled_password, SHA1_DIGEST_LENGTH);
389
SHA1Final(temp_hash, &ctx);
391
/* Next, XOR the result with what the client sent to get the original
392
single-hashed password. */
393
for (int x= 0; x < SHA1_DIGEST_LENGTH; x++)
394
temp_hash[x]= temp_hash[x] ^ scrambled_password[x];
396
/* Hash this result once more to get the double-hashed password again. */
398
SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
399
SHA1Final(scrambled_password_check, &ctx);
401
/* These should match for a successful auth. */
402
return memcmp(local_scrambled_password, scrambled_password_check, SHA1_DIGEST_LENGTH) == 0;
405
static int init(plugin::Context &context)
407
AuthLDAP *auth_ldap= new AuthLDAP("auth_ldap");
408
if (! auth_ldap->initialize())
410
errmsg_printf(ERRMSG_LVL_ERROR, _("Could not load auth ldap: %s\n"),
411
auth_ldap->getError().c_str());
416
context.add(auth_ldap);
420
static DRIZZLE_SYSVAR_STR(uri,
423
N_("URI of the LDAP server to contact"),
424
NULL, /* check func */
425
NULL, /* update func*/
428
static DRIZZLE_SYSVAR_STR(bind_dn,
431
N_("DN to use when binding to the LDAP server"),
432
NULL, /* check func */
433
NULL, /* update func*/
434
NULL); /* default value */
436
static DRIZZLE_SYSVAR_STR(bind_password,
439
N_("Password to use when binding the DN"),
440
NULL, /* check func */
441
NULL, /* update func*/
442
NULL); /* default value */
444
static DRIZZLE_SYSVAR_STR(base_dn,
447
N_("DN to use when searching"),
448
NULL, /* check func */
449
NULL, /* update func*/
450
NULL); /* default value */
452
static DRIZZLE_SYSVAR_STR(password_attribute,
455
N_("Attribute in LDAP with plain text password"),
456
NULL, /* check func */
457
NULL, /* update func*/
458
DEFAULT_PASSWORD_ATTRIBUTE);
460
static DRIZZLE_SYSVAR_STR(mysql_password_attribute,
461
mysql_password_attribute,
463
N_("Attribute in LDAP with MySQL hashed password"),
464
NULL, /* check func */
465
NULL, /* update func*/
466
DEFAULT_MYSQL_PASSWORD_ATTRIBUTE);
468
static DRIZZLE_SYSVAR_INT(cache_timeout,
471
N_("How often to empty the users cache, 0 to disable"),
472
NULL, /* check func */
473
NULL, /* update func */
474
DEFAULT_CACHE_TIMEOUT,
479
static drizzle_sys_var* sys_variables[]=
482
DRIZZLE_SYSVAR(bind_dn),
483
DRIZZLE_SYSVAR(bind_password),
484
DRIZZLE_SYSVAR(base_dn),
485
DRIZZLE_SYSVAR(password_attribute),
486
DRIZZLE_SYSVAR(mysql_password_attribute),
487
DRIZZLE_SYSVAR(cache_timeout),
491
} /* namespace auth_ldap */
493
DRIZZLE_PLUGIN(auth_ldap::init, auth_ldap::sys_variables);