~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to plugin/auth_ldap/auth_ldap.cc

  • Committer: Monty Taylor
  • Date: 2008-09-16 00:00:48 UTC
  • mto: This revision was merged to the branch mainline in revision 391.
  • Revision ID: monty@inaugust.com-20080916000048-3rvrv3gv9l0ad3gs
Fixed copyright headers in drizzled/

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
20
 
#include "config.h"
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
 
 
31
 
#include "drizzled/plugin/authentication.h"
32
 
#include "drizzled/identifier.h"
33
 
#include "drizzled/util/convert.h"
34
 
#include "drizzled/algorithm/sha1.h"
35
 
 
36
 
#include <drizzled/module/option_map.h>
37
 
#include <boost/program_options.hpp>
38
 
 
39
 
namespace po= boost::program_options;
40
 
using namespace std;
41
 
using namespace drizzled;
42
 
 
43
 
namespace auth_ldap
44
 
{
45
 
 
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";
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;
58
 
 
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
 
 
95
 
  typedef std::pair<PasswordType, std::string> PasswordEntry;
96
 
  typedef std::pair<std::string, PasswordEntry> UserEntry;
97
 
  typedef std::map<std::string, PasswordEntry> UserCache;
98
 
 
99
 
  /**
100
 
   * Base class method to check authentication for a user.
101
 
   */
102
 
  bool authenticate(const identifier::User &sctx, const string &password);
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
 
{
163
 
  int return_code= ldap_initialize(&ldap, (char *)uri.c_str());
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
 
 
182
 
  if (not bind_dn.empty())
183
 
  {
184
 
    return_code= ldap_simple_bind_s(ldap, (char *)bind_dn.c_str(), (char *)bind_password.c_str());
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
 
 
203
 
bool AuthLDAP::authenticate(const identifier::User &sctx, const string &password)
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
 
 
225
 
  AuthLDAP::UserCache::const_iterator user= users.find(sctx.username());
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. */
233
 
    user= users.find(sctx.username());
234
 
    if (user == users.end())
235
 
      lookupUser(sctx.username());
236
 
 
237
 
    pthread_rwlock_unlock(&lock);
238
 
 
239
 
    pthread_rwlock_rdlock(&lock);
240
 
 
241
 
    /* Get user again because map may have changed while unlocked. */
242
 
    user= users.find(sctx.username());
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
 
 
256
 
  if (sctx.getPasswordType() == identifier::User::MYSQL_HASH)
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
 
  {
278
 
    (char *)password_attribute.c_str(),
279
 
    (char *)mysql_password_attribute.c_str(),
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
 
      {
291
 
        errmsg_printf(error::ERROR, _("Reconnect failed: %s\n"),
292
 
                      getError().c_str());
293
 
        return;
294
 
      }
295
 
    }
296
 
 
297
 
    int return_code= ldap_search_ext_s(ldap,
298
 
                                       (char *)base_dn.c_str(),
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
 
    {
310
 
      errmsg_printf(error::ERROR, _("ldap_search_ext_s failed: %s\n"),
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
 
  {
334
 
    char **values= ldap_get_values(ldap, entry, (char *)mysql_password_attribute.c_str());
335
 
    if (values == NULL)
336
 
    {
337
 
      values= ldap_get_values(ldap, entry, (char *)password_attribute.c_str());
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
 
 
411
 
static int init(module::Context &context)
412
 
{
413
 
  AuthLDAP *auth_ldap= new AuthLDAP("auth_ldap");
414
 
  if (! auth_ldap->initialize())
415
 
  {
416
 
    errmsg_printf(error::ERROR, _("Could not load auth ldap: %s\n"),
417
 
                  auth_ldap->getError().c_str());
418
 
    delete auth_ldap;
419
 
    return 1;
420
 
  }
421
 
 
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));
429
 
 
430
 
  context.add(auth_ldap);
431
 
  return 0;
432
 
}
433
 
 
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
 
 
452
 
} /* namespace auth_ldap */
453
 
 
454
 
DRIZZLE_PLUGIN(auth_ldap::init, NULL, auth_ldap::init_options);