Drizzled Public API Documentation

auth_file.cc
00001 /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
00002  *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
00003  *
00004  *  Copyright (C) 2010 Eric Day
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; version 2 of the License.
00009  *
00010  *  This program is distributed in the hope that it will be useful,
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  *  GNU General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU General Public License
00016  *  along with this program; if not, write to the Free Software
00017  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00018  */
00019 
00020 #include <config.h>
00021 
00022 #include <fstream>
00023 #include <map>
00024 #include <string>
00025 #include <iostream>
00026 
00027 #include <boost/program_options.hpp>
00028 #include <boost/filesystem.hpp>
00029 
00030 #include <drizzled/configmake.h>
00031 #include <drizzled/plugin/authentication.h>
00032 #include <drizzled/identifier.h>
00033 #include <drizzled/util/convert.h>
00034 #include <drizzled/algorithm/sha1.h>
00035 #include <drizzled/module/option_map.h>
00036 
00037 namespace po= boost::program_options;
00038 namespace fs= boost::filesystem;
00039 
00040 using namespace std;
00041 using namespace drizzled;
00042 
00043 namespace auth_file
00044 {
00045 
00046 static const fs::path DEFAULT_USERS_FILE= SYSCONFDIR "/drizzle.users";
00047 
00048 class AuthFile: public plugin::Authentication
00049 {
00050   const fs::path users_file;
00051 
00052 public:
00053 
00054   AuthFile(string name_arg, fs::path users_file_arg);
00055 
00059   string& getError(void);
00060 
00067   bool loadFile(void);
00068 
00069 private:
00070 
00074   bool authenticate(const identifier::User &sctx, const string &password);
00075 
00087   bool verifyMySQLHash(const string &password,
00088                        const string &scramble_bytes,
00089                        const string &scrambled_password);
00090 
00091   string error;
00092 
00096   std::map<string, string> users;
00097 };
00098 
00099 AuthFile::AuthFile(string name_arg, fs::path users_file_arg):
00100   plugin::Authentication(name_arg),
00101   users_file(users_file_arg),
00102   error(),
00103   users()
00104 {
00105 }
00106 
00107 string& AuthFile::getError(void)
00108 {
00109   return error;
00110 }
00111 
00112 bool AuthFile::loadFile(void)
00113 {
00114   ifstream file(users_file.string().c_str());
00115 
00116   if (!file.is_open())
00117   {
00118     error = "Could not open users file: ";
00119     error += users_file.string();
00120     return false;
00121   }
00122 
00123   while (!file.eof())
00124   {
00125     string line;
00126     getline(file, line);
00127 
00128     /* Ignore blank lines and lines starting with '#'. */
00129     if (line == "" || line[line.find_first_not_of(" \t")] == '#')
00130       continue;
00131 
00132     string username;
00133     string password;
00134     size_t password_offset = line.find(":");
00135     if (password_offset == string::npos)
00136       username = line;
00137     else
00138     {
00139       username = string(line, 0, password_offset);
00140       password = string(line, password_offset + 1);
00141     }
00142 
00143     std::pair<std::map<std::string, std::string>::iterator, bool> result=
00144       users.insert(std::pair<std::string, std::string>(username, password));
00145 
00146     if (result.second == false)
00147     {
00148       error = "Duplicate entry found in users file: ";
00149       error += username;
00150       file.close();
00151       return false;
00152     }
00153   }
00154 
00155   file.close();
00156   return true;
00157 }
00158 
00159 bool AuthFile::verifyMySQLHash(const string &password,
00160                                const string &scramble_bytes,
00161                                const string &scrambled_password)
00162 {
00163   if (scramble_bytes.size() != SHA1_DIGEST_LENGTH ||
00164       scrambled_password.size() != SHA1_DIGEST_LENGTH)
00165   {
00166     return false;
00167   }
00168 
00169   SHA1_CTX ctx;
00170   uint8_t local_scrambled_password[SHA1_DIGEST_LENGTH];
00171   uint8_t temp_hash[SHA1_DIGEST_LENGTH];
00172   uint8_t scrambled_password_check[SHA1_DIGEST_LENGTH];
00173 
00174   /* Generate the double SHA1 hash for the password stored locally first. */
00175   SHA1Init(&ctx);
00176   SHA1Update(&ctx, reinterpret_cast<const uint8_t *>(password.c_str()),
00177              password.size());
00178   SHA1Final(temp_hash, &ctx);
00179 
00180   SHA1Init(&ctx);
00181   SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
00182   SHA1Final(local_scrambled_password, &ctx);
00183 
00184   /* Hash the scramble that was sent to client with the local password. */
00185   SHA1Init(&ctx);
00186   SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()),
00187              SHA1_DIGEST_LENGTH);
00188   SHA1Update(&ctx, local_scrambled_password, SHA1_DIGEST_LENGTH);
00189   SHA1Final(temp_hash, &ctx);
00190 
00191   /* Next, XOR the result with what the client sent to get the original
00192      single-hashed password. */
00193   for (int x= 0; x < SHA1_DIGEST_LENGTH; x++)
00194     temp_hash[x]= temp_hash[x] ^ scrambled_password[x];
00195 
00196   /* Hash this result once more to get the double-hashed password again. */
00197   SHA1Init(&ctx);
00198   SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
00199   SHA1Final(scrambled_password_check, &ctx);
00200 
00201   /* These should match for a successful auth. */
00202   return memcmp(local_scrambled_password, scrambled_password_check, SHA1_DIGEST_LENGTH) == 0;
00203 }
00204 
00205 bool AuthFile::authenticate(const identifier::User &sctx, const string &password)
00206 {
00207   std::map<std::string, std::string>::const_iterator user= users.find(sctx.username());
00208   if (user == users.end())
00209     return false;
00210 
00211   if (sctx.getPasswordType() == identifier::User::MYSQL_HASH)
00212     return verifyMySQLHash(user->second, sctx.getPasswordContext(), password);
00213 
00214   if (password == user->second)
00215     return true;
00216 
00217   return false;
00218 }
00219 
00220 static int init(module::Context &context)
00221 {
00222   const module::option_map &vm= context.getOptions();
00223 
00224   AuthFile *auth_file = new AuthFile("auth_file", fs::path(vm["users"].as<string>()));
00225   if (not auth_file->loadFile())
00226   {
00227     errmsg_printf(error::ERROR, _("Could not load auth file: %s\n"),
00228                   auth_file->getError().c_str());
00229     delete auth_file;
00230     return 1;
00231   }
00232 
00233   context.add(auth_file);
00234   context.registerVariable(new sys_var_const_string_val("users", vm["users"].as<string>()));
00235 
00236   return 0;
00237 }
00238 
00239 
00240 static void init_options(drizzled::module::option_context &context)
00241 {
00242   context("users", 
00243           po::value<string>()->default_value(DEFAULT_USERS_FILE.string()),
00244           N_("File to load for usernames and passwords"));
00245 }
00246 
00247 } /* namespace auth_file */
00248 
00249 DRIZZLE_PLUGIN(auth_file::init, NULL, auth_file::init_options);