00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include <config.h>
00021
00022
00023 #define LDAP_DEPRECATED 1
00024
00025 #include <ldap.h>
00026 #include <pthread.h>
00027 #include <sys/time.h>
00028 #include <map>
00029 #include <string>
00030
00031 #include <drizzled/plugin/authentication.h>
00032 #include <drizzled/identifier.h>
00033 #include <drizzled/util/convert.h>
00034 #include <drizzled/algorithm/sha1.h>
00035
00036 #include <drizzled/module/option_map.h>
00037 #include <boost/program_options.hpp>
00038
00039 namespace po= boost::program_options;
00040 using namespace std;
00041 using namespace drizzled;
00042
00043 namespace auth_ldap
00044 {
00045
00046 std::string uri;
00047 const std::string DEFAULT_URI= "ldap://127.0.0.1/";
00048 std::string bind_dn;
00049 std::string bind_password;
00050 std::string base_dn;
00051 std::string password_attribute;
00052 std::string DEFAULT_PASSWORD_ATTRIBUTE= "userPassword";
00053 std::string mysql_password_attribute;
00054 const std::string DEFAULT_MYSQL_PASSWORD_ATTRIBUTE= "mysqlUserPassword";
00055 static const int DEFAULT_CACHE_TIMEOUT= 600;
00056 typedef constrained_check<int, DEFAULT_CACHE_TIMEOUT, 0, 2147483647> cachetimeout_constraint;
00057 static cachetimeout_constraint cache_timeout= 0;
00058
00059
00060 class AuthLDAP: public plugin::Authentication
00061 {
00062 public:
00063
00064 AuthLDAP(string name_arg);
00065 ~AuthLDAP();
00066
00072 bool initialize(void);
00073
00079 bool connect(void);
00080
00084 string& getError(void);
00085
00086 private:
00087
00088 typedef enum
00089 {
00090 NOT_FOUND,
00091 PLAIN_TEXT,
00092 MYSQL_HASH
00093 } PasswordType;
00094
00095 typedef std::pair<PasswordType, std::string> PasswordEntry;
00096 typedef std::pair<std::string, PasswordEntry> UserEntry;
00097 typedef std::map<std::string, PasswordEntry> UserCache;
00098
00102 bool authenticate(const identifier::User &sctx, const string &password);
00103
00109 void lookupUser(const string& user);
00110
00122 bool verifyMySQLHash(const PasswordEntry &password,
00123 const string &scramble_bytes,
00124 const string &scrambled_password);
00125
00126 time_t next_cache_expiration;
00127 LDAP *ldap;
00128 string error;
00129 UserCache users;
00130 pthread_rwlock_t lock;
00131 };
00132
00133 AuthLDAP::AuthLDAP(string name_arg):
00134 plugin::Authentication(name_arg),
00135 next_cache_expiration(),
00136 ldap(),
00137 error(),
00138 users()
00139 {
00140 }
00141
00142 AuthLDAP::~AuthLDAP()
00143 {
00144 pthread_rwlock_destroy(&lock);
00145 if (ldap != NULL)
00146 ldap_unbind(ldap);
00147 }
00148
00149 bool AuthLDAP::initialize(void)
00150 {
00151 int return_code= pthread_rwlock_init(&lock, NULL);
00152 if (return_code != 0)
00153 {
00154 error= "pthread_rwlock_init failed";
00155 return false;
00156 }
00157
00158 return connect();
00159 }
00160
00161 bool AuthLDAP::connect(void)
00162 {
00163 int return_code= ldap_initialize(&ldap, (char *)uri.c_str());
00164 if (return_code != LDAP_SUCCESS)
00165 {
00166 error= "ldap_initialize failed: ";
00167 error+= ldap_err2string(return_code);
00168 return false;
00169 }
00170
00171 int version= 3;
00172 return_code= ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
00173 if (return_code != LDAP_SUCCESS)
00174 {
00175 ldap_unbind(ldap);
00176 ldap= NULL;
00177 error= "ldap_set_option failed: ";
00178 error+= ldap_err2string(return_code);
00179 return false;
00180 }
00181
00182 if (not bind_dn.empty())
00183 {
00184 return_code= ldap_simple_bind_s(ldap, (char *)bind_dn.c_str(), (char *)bind_password.c_str());
00185 if (return_code != LDAP_SUCCESS)
00186 {
00187 ldap_unbind(ldap);
00188 ldap= NULL;
00189 error= "ldap_simple_bind_s failed: ";
00190 error+= ldap_err2string(return_code);
00191 return false;
00192 }
00193 }
00194
00195 return true;
00196 }
00197
00198 string& AuthLDAP::getError(void)
00199 {
00200 return error;
00201 }
00202
00203 bool AuthLDAP::authenticate(const identifier::User &sctx, const string &password)
00204 {
00205
00206 if (cache_timeout > 0)
00207 {
00208 struct timeval current_time;
00209 gettimeofday(¤t_time, NULL);
00210 if (current_time.tv_sec > next_cache_expiration)
00211 {
00212 pthread_rwlock_wrlock(&lock);
00213
00214 if (current_time.tv_sec > next_cache_expiration)
00215 {
00216 users.clear();
00217 next_cache_expiration= current_time.tv_sec + cache_timeout;
00218 }
00219 pthread_rwlock_unlock(&lock);
00220 }
00221 }
00222
00223 pthread_rwlock_rdlock(&lock);
00224
00225 AuthLDAP::UserCache::const_iterator user= users.find(sctx.username());
00226 if (user == users.end())
00227 {
00228 pthread_rwlock_unlock(&lock);
00229
00230 pthread_rwlock_wrlock(&lock);
00231
00232
00233 user= users.find(sctx.username());
00234 if (user == users.end())
00235 lookupUser(sctx.username());
00236
00237 pthread_rwlock_unlock(&lock);
00238
00239 pthread_rwlock_rdlock(&lock);
00240
00241
00242 user= users.find(sctx.username());
00243 if (user == users.end())
00244 {
00245 pthread_rwlock_unlock(&lock);
00246 return false;
00247 }
00248 }
00249
00250 if (user->second.first == NOT_FOUND)
00251 {
00252 pthread_rwlock_unlock(&lock);
00253 return false;
00254 }
00255
00256 if (sctx.getPasswordType() == identifier::User::MYSQL_HASH)
00257 {
00258 bool allow= verifyMySQLHash(user->second, sctx.getPasswordContext(), password);
00259 pthread_rwlock_unlock(&lock);
00260 return allow;
00261 }
00262
00263 if (user->second.first == PLAIN_TEXT && password == user->second.second)
00264 {
00265 pthread_rwlock_unlock(&lock);
00266 return true;
00267 }
00268
00269 pthread_rwlock_unlock(&lock);
00270 return false;
00271 }
00272
00273 void AuthLDAP::lookupUser(const string& user)
00274 {
00275 string filter("(cn=" + user + ")");
00276 const char *attributes[3]=
00277 {
00278 (char *)password_attribute.c_str(),
00279 (char *)mysql_password_attribute.c_str(),
00280 NULL
00281 };
00282 LDAPMessage *result;
00283 bool try_reconnect= true;
00284
00285 while (true)
00286 {
00287 if (ldap == NULL)
00288 {
00289 if (! connect())
00290 {
00291 errmsg_printf(error::ERROR, _("Reconnect failed: %s\n"),
00292 getError().c_str());
00293 return;
00294 }
00295 }
00296
00297 int return_code= ldap_search_ext_s(ldap,
00298 (char *)base_dn.c_str(),
00299 LDAP_SCOPE_ONELEVEL,
00300 filter.c_str(),
00301 const_cast<char **>(attributes),
00302 0,
00303 NULL,
00304 NULL,
00305 NULL,
00306 1,
00307 &result);
00308 if (return_code != LDAP_SUCCESS)
00309 {
00310 errmsg_printf(error::ERROR, _("ldap_search_ext_s failed: %s\n"),
00311 ldap_err2string(return_code));
00312
00313
00314 if (try_reconnect)
00315 {
00316 try_reconnect= false;
00317 ldap_unbind(ldap);
00318 ldap= NULL;
00319 continue;
00320 }
00321
00322 return;
00323 }
00324
00325 break;
00326 }
00327
00328 LDAPMessage *entry= ldap_first_entry(ldap, result);
00329 AuthLDAP::PasswordEntry new_password;
00330 if (entry == NULL)
00331 new_password= AuthLDAP::PasswordEntry(NOT_FOUND, "");
00332 else
00333 {
00334 char **values= ldap_get_values(ldap, entry, (char *)mysql_password_attribute.c_str());
00335 if (values == NULL)
00336 {
00337 values= ldap_get_values(ldap, entry, (char *)password_attribute.c_str());
00338 if (values == NULL)
00339 new_password= AuthLDAP::PasswordEntry(NOT_FOUND, "");
00340 else
00341 {
00342 new_password= AuthLDAP::PasswordEntry(PLAIN_TEXT, values[0]);
00343 ldap_value_free(values);
00344 }
00345 }
00346 else
00347 {
00348 new_password= AuthLDAP::PasswordEntry(MYSQL_HASH, values[0]);
00349 ldap_value_free(values);
00350 }
00351 }
00352
00353 users.insert(AuthLDAP::UserEntry(user, new_password));
00354 }
00355
00356 bool AuthLDAP::verifyMySQLHash(const PasswordEntry &password,
00357 const string &scramble_bytes,
00358 const string &scrambled_password)
00359 {
00360 if (scramble_bytes.size() != SHA1_DIGEST_LENGTH ||
00361 scrambled_password.size() != SHA1_DIGEST_LENGTH)
00362 {
00363 return false;
00364 }
00365
00366 SHA1_CTX ctx;
00367 uint8_t local_scrambled_password[SHA1_DIGEST_LENGTH];
00368 uint8_t temp_hash[SHA1_DIGEST_LENGTH];
00369 uint8_t scrambled_password_check[SHA1_DIGEST_LENGTH];
00370
00371 if (password.first == MYSQL_HASH)
00372 {
00373
00374 drizzled_hex_to_string(reinterpret_cast<char*>(local_scrambled_password),
00375 password.second.c_str(), SHA1_DIGEST_LENGTH * 2);
00376 }
00377 else
00378 {
00379
00380 SHA1Init(&ctx);
00381 SHA1Update(&ctx, reinterpret_cast<const uint8_t *>(password.second.c_str()),
00382 password.second.size());
00383 SHA1Final(temp_hash, &ctx);
00384
00385 SHA1Init(&ctx);
00386 SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
00387 SHA1Final(local_scrambled_password, &ctx);
00388 }
00389
00390
00391 SHA1Init(&ctx);
00392 SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()),
00393 SHA1_DIGEST_LENGTH);
00394 SHA1Update(&ctx, local_scrambled_password, SHA1_DIGEST_LENGTH);
00395 SHA1Final(temp_hash, &ctx);
00396
00397
00398
00399 for (int x= 0; x < SHA1_DIGEST_LENGTH; x++)
00400 temp_hash[x]= temp_hash[x] ^ scrambled_password[x];
00401
00402
00403 SHA1Init(&ctx);
00404 SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
00405 SHA1Final(scrambled_password_check, &ctx);
00406
00407
00408 return memcmp(local_scrambled_password, scrambled_password_check, SHA1_DIGEST_LENGTH) == 0;
00409 }
00410
00411 static int init(module::Context &context)
00412 {
00413 AuthLDAP *auth_ldap= new AuthLDAP("auth_ldap");
00414 if (! auth_ldap->initialize())
00415 {
00416 errmsg_printf(error::ERROR, _("Could not load auth ldap: %s\n"),
00417 auth_ldap->getError().c_str());
00418 delete auth_ldap;
00419 return 1;
00420 }
00421
00422 context.registerVariable(new sys_var_const_string_val("uri", uri));
00423 context.registerVariable(new sys_var_const_string_val("bind-dn", bind_dn));
00424 context.registerVariable(new sys_var_const_string_val("bind-password", bind_password));
00425 context.registerVariable(new sys_var_const_string_val("base-dn", base_dn));
00426 context.registerVariable(new sys_var_const_string_val("password-attribute",password_attribute));
00427 context.registerVariable(new sys_var_const_string_val("mysql-password-attribute", mysql_password_attribute));
00428 context.registerVariable(new sys_var_constrained_value_readonly<int>("cache-timeout", cache_timeout));
00429
00430 context.add(auth_ldap);
00431 return 0;
00432 }
00433
00434 static void init_options(drizzled::module::option_context &context)
00435 {
00436 context("uri", po::value<string>(&uri)->default_value(DEFAULT_URI),
00437 N_("URI of the LDAP server to contact"));
00438 context("bind-db", po::value<string>(&bind_dn)->default_value(""),
00439 N_("DN to use when binding to the LDAP server"));
00440 context("bind-password", po::value<string>(&bind_password)->default_value(""),
00441 N_("Password to use when binding the DN"));
00442 context("base-dn", po::value<string>(&base_dn)->default_value(""),
00443 N_("DN to use when searching"));
00444 context("password-attribute", po::value<string>(&password_attribute)->default_value(DEFAULT_PASSWORD_ATTRIBUTE),
00445 N_("Attribute in LDAP with plain text password"));
00446 context("mysql-password-attribute", po::value<string>(&mysql_password_attribute)->default_value(DEFAULT_MYSQL_PASSWORD_ATTRIBUTE),
00447 N_("Attribute in LDAP with MySQL hashed password"));
00448 context("cache-timeout", po::value<cachetimeout_constraint>(&cache_timeout)->default_value(DEFAULT_CACHE_TIMEOUT),
00449 N_("How often to empty the users cache, 0 to disable"));
00450 }
00451
00452 }
00453
00454 DRIZZLE_PLUGIN(auth_ldap::init, NULL, auth_ldap::init_options);