~drizzle-trunk/drizzle/development

1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
1
/* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2
 *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3
 *
4
 *  Copyright (C) 2010 Eric Day
5
 *
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.
9
 *
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.
14
 *
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
18
 */
19
2173.2.1 by Monty Taylor
Fixes incorrect usage of include
20
#include <config.h>
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
21
22
/* This is needed for simple auth, we're not ready for SASL yet. */
23
#define LDAP_DEPRECATED 1
24
25
#include <ldap.h>
26
#include <pthread.h>
27
#include <sys/time.h>
28
#include <map>
29
#include <string>
30
2173.2.1 by Monty Taylor
Fixes incorrect usage of include
31
#include <drizzled/plugin/authentication.h>
32
#include <drizzled/identifier.h>
33
#include <drizzled/util/convert.h>
34
#include <drizzled/algorithm/sha1.h>
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
35
1970.1.2 by vjsamuel1990 at gmail
Merge introduction of variables_map into auth_ldap
36
#include <drizzled/module/option_map.h>
37
#include <boost/program_options.hpp>
38
39
namespace po= boost::program_options;
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
40
using namespace std;
41
using namespace drizzled;
42
43
namespace auth_ldap
44
{
45
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
46
std::string uri;
47
const std::string DEFAULT_URI= "ldap://127.0.0.1/";
48
std::string bind_dn;
49
std::string bind_password;
50
std::string base_dn;
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";
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
55
static const int DEFAULT_CACHE_TIMEOUT= 600;
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
56
typedef constrained_check<int, DEFAULT_CACHE_TIMEOUT, 0, 2147483647> cachetimeout_constraint;
57
static cachetimeout_constraint cache_timeout= 0;
58
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
59
60
class AuthLDAP: public plugin::Authentication
61
{
62
public:
63
64
  AuthLDAP(string name_arg);
65
  ~AuthLDAP();
66
67
  /**
68
   * Initialize the LDAP connection.
69
   *
70
   * @return True on success, false otherwise.
71
   */
72
  bool initialize(void);
73
74
  /**
75
   * Connect to the LDAP server.
76
   *
77
   * @return True on success, false otherwise.
78
   */
79
  bool connect(void);
80
81
  /**
82
   * Retrieve the last error encountered in the class.
83
   */
84
  string& getError(void);
85
86
private:
87
88
  typedef enum
89
  {
90
    NOT_FOUND,
91
    PLAIN_TEXT,
92
    MYSQL_HASH
93
  } PasswordType;
94
1966.2.6 by Brian Aker
This is from the catalog patch (I'm pushing it up as its own little thing
95
  typedef std::pair<PasswordType, std::string> PasswordEntry;
96
  typedef std::pair<std::string, PasswordEntry> UserEntry;
97
  typedef std::map<std::string, PasswordEntry> UserCache;
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
98
99
  /**
100
   * Base class method to check authentication for a user.
101
   */
2008.1.1 by Brian Aker
Adding user identifier that makes use of a shared ptr to handle concurrency
102
  bool authenticate(const identifier::User &sctx, const string &password);
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
103
104
  /**
105
   * Lookup a user in LDAP.
106
   *
107
   * @param[in] Username to lookup.
108
   */
109
  void lookupUser(const string& user);
110
111
  /**
112
   * Verify the local and remote scrambled password match using the MySQL
113
   * hashing algorithm.
114
   *
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
119
   *  password remotely.
120
   * @return True if the password matched, false if not.
121
   */
122
  bool verifyMySQLHash(const PasswordEntry &password,
123
                       const string &scramble_bytes,
124
                       const string &scrambled_password);
125
126
  time_t next_cache_expiration;
127
  LDAP *ldap;
128
  string error;
129
  UserCache users;
130
  pthread_rwlock_t lock;
131
};
132
133
AuthLDAP::AuthLDAP(string name_arg):
134
  plugin::Authentication(name_arg),
135
  next_cache_expiration(),
136
  ldap(),
137
  error(),
138
  users()
139
{
140
}
141
142
AuthLDAP::~AuthLDAP()
143
{
144
  pthread_rwlock_destroy(&lock);
145
  if (ldap != NULL)
146
    ldap_unbind(ldap);
147
}
148
149
bool AuthLDAP::initialize(void)
150
{
151
  int return_code= pthread_rwlock_init(&lock, NULL);
152
  if (return_code != 0)
153
  {
154
    error= "pthread_rwlock_init failed";
155
    return false;
156
  }
157
158
  return connect();
159
}
160
161
bool AuthLDAP::connect(void)
162
{
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
163
  int return_code= ldap_initialize(&ldap, (char *)uri.c_str());
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
164
  if (return_code != LDAP_SUCCESS)
165
  {
166
    error= "ldap_initialize failed: ";
167
    error+= ldap_err2string(return_code);
168
    return false;
169
  }
170
171
  int version= 3;
172
  return_code= ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
173
  if (return_code != LDAP_SUCCESS)
174
  {
175
    ldap_unbind(ldap);
176
    ldap= NULL;
177
    error= "ldap_set_option failed: ";
178
    error+= ldap_err2string(return_code);
179
    return false;
180
  }
181
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
182
  if (not bind_dn.empty())
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
183
  {
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
184
    return_code= ldap_simple_bind_s(ldap, (char *)bind_dn.c_str(), (char *)bind_password.c_str());
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
185
    if (return_code != LDAP_SUCCESS)
186
    {
187
      ldap_unbind(ldap);
188
      ldap= NULL;
189
      error= "ldap_simple_bind_s failed: ";
190
      error+= ldap_err2string(return_code);
191
      return false;
192
    }
193
  }
194
195
  return true;
196
}
197
198
string& AuthLDAP::getError(void)
199
{
200
  return error;
201
}
202
2008.1.1 by Brian Aker
Adding user identifier that makes use of a shared ptr to handle concurrency
203
bool AuthLDAP::authenticate(const identifier::User &sctx, const string &password)
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
204
{
205
  /* See if cache should be emptied. */
206
  if (cache_timeout > 0)
207
  {
208
    struct timeval current_time;
209
    gettimeofday(&current_time, NULL);
210
    if (current_time.tv_sec > next_cache_expiration)
211
    {
212
      pthread_rwlock_wrlock(&lock);
213
      /* Make sure another thread didn't already clear it. */
214
      if (current_time.tv_sec > next_cache_expiration)
215
      {
216
        users.clear();
217
        next_cache_expiration= current_time.tv_sec + cache_timeout;
218
      }
219
      pthread_rwlock_unlock(&lock);
220
    }
221
  }
222
223
  pthread_rwlock_rdlock(&lock);
224
2008.1.1 by Brian Aker
Adding user identifier that makes use of a shared ptr to handle concurrency
225
  AuthLDAP::UserCache::const_iterator user= users.find(sctx.username());
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
226
  if (user == users.end())
227
  {
228
    pthread_rwlock_unlock(&lock);
229
230
    pthread_rwlock_wrlock(&lock);
231
232
    /* Make sure the user was not added while we unlocked. */
2008.1.1 by Brian Aker
Adding user identifier that makes use of a shared ptr to handle concurrency
233
    user= users.find(sctx.username());
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
234
    if (user == users.end())
2008.1.1 by Brian Aker
Adding user identifier that makes use of a shared ptr to handle concurrency
235
      lookupUser(sctx.username());
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
236
237
    pthread_rwlock_unlock(&lock);
238
239
    pthread_rwlock_rdlock(&lock);
240
241
    /* Get user again because map may have changed while unlocked. */
2008.1.1 by Brian Aker
Adding user identifier that makes use of a shared ptr to handle concurrency
242
    user= users.find(sctx.username());
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
243
    if (user == users.end())
244
    {
245
      pthread_rwlock_unlock(&lock);
246
      return false;
247
    }
248
  }
249
250
  if (user->second.first == NOT_FOUND)
251
  {
252
    pthread_rwlock_unlock(&lock);
253
    return false;
254
  }
255
2008.1.1 by Brian Aker
Adding user identifier that makes use of a shared ptr to handle concurrency
256
  if (sctx.getPasswordType() == identifier::User::MYSQL_HASH)
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
257
  {
258
    bool allow= verifyMySQLHash(user->second, sctx.getPasswordContext(), password);
259
    pthread_rwlock_unlock(&lock);
260
    return allow;
261
  }
262
263
  if (user->second.first == PLAIN_TEXT && password == user->second.second)
264
  {
265
    pthread_rwlock_unlock(&lock);
266
    return true;
267
  }
268
269
  pthread_rwlock_unlock(&lock);
270
  return false;
271
}
272
273
void AuthLDAP::lookupUser(const string& user)
274
{
275
  string filter("(cn=" + user + ")");
276
  const char *attributes[3]=
277
  {
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
278
    (char *)password_attribute.c_str(),
279
    (char *)mysql_password_attribute.c_str(),
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
280
    NULL
281
  };
282
  LDAPMessage *result;
283
  bool try_reconnect= true;
284
285
  while (true)
286
  {
287
    if (ldap == NULL)
288
    {
289
      if (! connect())
290
      {
2126.3.3 by Brian Aker
Merge in error message rework. Many error messages are fixed in this patch.
291
        errmsg_printf(error::ERROR, _("Reconnect failed: %s\n"),
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
292
                      getError().c_str());
293
        return;
294
      }
295
    }
296
297
    int return_code= ldap_search_ext_s(ldap,
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
298
                                       (char *)base_dn.c_str(),
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
299
                                       LDAP_SCOPE_ONELEVEL,
300
                                       filter.c_str(),
301
                                       const_cast<char **>(attributes),
302
                                       0,
303
                                       NULL,
304
                                       NULL,
305
                                       NULL,
306
                                       1,
307
                                       &result);
308
    if (return_code != LDAP_SUCCESS)
309
    {
2126.3.3 by Brian Aker
Merge in error message rework. Many error messages are fixed in this patch.
310
      errmsg_printf(error::ERROR, _("ldap_search_ext_s failed: %s\n"),
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
311
                    ldap_err2string(return_code));
312
313
      /* Only try one reconnect per request. */
314
      if (try_reconnect)
315
      {
316
        try_reconnect= false;
317
        ldap_unbind(ldap);
318
        ldap= NULL;
319
        continue;
320
      }
321
322
      return;
323
    }
324
325
    break;
326
  }
327
328
  LDAPMessage *entry= ldap_first_entry(ldap, result);
329
  AuthLDAP::PasswordEntry new_password;
330
  if (entry == NULL)
331
    new_password= AuthLDAP::PasswordEntry(NOT_FOUND, "");
332
  else
333
  {
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
334
    char **values= ldap_get_values(ldap, entry, (char *)mysql_password_attribute.c_str());
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
335
    if (values == NULL)
336
    {
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
337
      values= ldap_get_values(ldap, entry, (char *)password_attribute.c_str());
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
338
      if (values == NULL)
339
        new_password= AuthLDAP::PasswordEntry(NOT_FOUND, "");
340
      else
341
      {
342
        new_password= AuthLDAP::PasswordEntry(PLAIN_TEXT, values[0]);
343
        ldap_value_free(values);
344
      }
345
    }
346
    else
347
    {
348
      new_password= AuthLDAP::PasswordEntry(MYSQL_HASH, values[0]);
349
      ldap_value_free(values);
350
    }
351
  }
352
353
  users.insert(AuthLDAP::UserEntry(user, new_password));
354
}
355
356
bool AuthLDAP::verifyMySQLHash(const PasswordEntry &password,
357
                               const string &scramble_bytes,
358
                               const string &scrambled_password)
359
{
360
  if (scramble_bytes.size() != SHA1_DIGEST_LENGTH ||
361
      scrambled_password.size() != SHA1_DIGEST_LENGTH)
362
  {
363
    return false;
364
  }
365
366
  SHA1_CTX ctx;
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];
370
371
  if (password.first == MYSQL_HASH)
372
  {
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);
376
  }
377
  else
378
  {
379
    /* Generate the double SHA1 hash for the password stored locally first. */
380
    SHA1Init(&ctx);
381
    SHA1Update(&ctx, reinterpret_cast<const uint8_t *>(password.second.c_str()),
382
               password.second.size());
383
    SHA1Final(temp_hash, &ctx);
384
385
    SHA1Init(&ctx);
386
    SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
387
    SHA1Final(local_scrambled_password, &ctx);
388
  }
389
390
  /* Hash the scramble that was sent to client with the local password. */
391
  SHA1Init(&ctx);
392
  SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()),
393
             SHA1_DIGEST_LENGTH);
394
  SHA1Update(&ctx, local_scrambled_password, SHA1_DIGEST_LENGTH);
395
  SHA1Final(temp_hash, &ctx);
396
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];
401
402
  /* Hash this result once more to get the double-hashed password again. */
403
  SHA1Init(&ctx);
404
  SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
405
  SHA1Final(scrambled_password_check, &ctx);
406
407
  /* These should match for a successful auth. */
408
  return memcmp(local_scrambled_password, scrambled_password_check, SHA1_DIGEST_LENGTH) == 0;
409
}
410
1530.2.6 by Monty Taylor
Moved plugin::Context to module::Context.
411
static int init(module::Context &context)
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
412
{
413
  AuthLDAP *auth_ldap= new AuthLDAP("auth_ldap");
414
  if (! auth_ldap->initialize())
415
  {
2126.3.3 by Brian Aker
Merge in error message rework. Many error messages are fixed in this patch.
416
    errmsg_printf(error::ERROR, _("Could not load auth ldap: %s\n"),
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
417
                  auth_ldap->getError().c_str());
418
    delete auth_ldap;
419
    return 1;
420
  }
421
1970.1.2 by vjsamuel1990 at gmail
Merge introduction of variables_map into auth_ldap
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));
1970.1.1 by vjsamuel1990 at gmail
Merge sysvar changes for auth_ldap
429
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
430
  context.add(auth_ldap);
431
  return 0;
432
}
433
1970.1.2 by vjsamuel1990 at gmail
Merge introduction of variables_map into auth_ldap
434
static void init_options(drizzled::module::option_context &context)
435
{
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"));
450
}
451
1497.2.2 by Eric Day
Initial auth_ldap plugin. There are no test cases since this would require a pre-configured LDAP server running. Testing will instead be done on one of the build hosts that has been configured with LDAP and a separate test script.
452
} /* namespace auth_ldap */
453
1970.1.2 by vjsamuel1990 at gmail
Merge introduction of variables_map into auth_ldap
454
DRIZZLE_PLUGIN(auth_ldap::init, NULL, auth_ldap::init_options);