Drizzled Public API Documentation

auth_ldap.cc
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>
35 
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://localhost/";
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= "drizzleMysqlUserPassword";
55 static const int DEFAULT_CACHE_TIMEOUT= 600;
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 
72  bool initialize(void);
73 
79  bool connect(void);
80 
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 
102  bool authenticate(const identifier::User &sctx, const string &password);
103 
109  void lookupUser(const string& user);
110 
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 
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 
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("(uid=" + 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-dn", 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_DECLARE_PLUGIN
455 {
456  DRIZZLE_VERSION_ID,
457  "auth_ldap",
458  "0.2",
459  "Eric Day, Henrik Ingo, Edward Konetzko",
460  N_("Authentication against an LDAP server"),
461  PLUGIN_LICENSE_GPL,
462  auth_ldap::init,
463  NULL,
464  auth_ldap::init_options
465 }
466 DRIZZLE_DECLARE_PLUGIN_END;
467 
A set of Session members describing the current authenticated user.
Definition: user.h:34
SHA1 Declarations.
bool initialize(void)
Definition: auth_ldap.cc:149
void lookupUser(const string &user)
Definition: auth_ldap.cc:273
TODO: Rename this file - func.h is stupid.
An Proxy Wrapper around boost::program_options::variables_map.
bool authenticate(const identifier::User &sctx, const string &password)
Definition: auth_ldap.cc:203
bool verifyMySQLHash(const PasswordEntry &password, const string &scramble_bytes, const string &scrambled_password)
Definition: auth_ldap.cc:356
Definition: engine.cc:41
bool connect(void)
Definition: auth_ldap.cc:161
string & getError(void)
Definition: auth_ldap.cc:198