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 static const fs::path DEFAULT_USERS_FILE= SYSCONFDIR "/drizzle.users";
00046 
00047 class AuthFile : public plugin::Authentication
00048 {
00049 public:
00050   AuthFile(fs::path users_file_arg);
00051 
00055   const string& getError() const;
00056 
00063   bool loadFile();
00064 
00065 private:
00066 
00070   bool authenticate(const identifier::User &sctx, const string &password);
00071 
00083   bool verifyMySQLHash(const string &password,
00084                        const string &scramble_bytes,
00085                        const string &scrambled_password);
00086 
00087   string error;
00088   const fs::path users_file;
00089 
00093   typedef std::map<string, string> users_t;
00094   users_t users;
00095 };
00096 
00097 AuthFile::AuthFile(fs::path users_file_arg) :
00098   plugin::Authentication("auth_file"),
00099   users_file(users_file_arg)
00100 {
00101 }
00102 
00103 const string& AuthFile::getError() const
00104 {
00105   return error;
00106 }
00107 
00108 bool AuthFile::loadFile()
00109 {
00110   ifstream file(users_file.string().c_str());
00111 
00112   if (!file.is_open())
00113   {
00114     error = "Could not open users file: " + users_file.string();
00115     return false;
00116   }
00117 
00118   string line;
00119   while (getline(file, line))
00120   {
00121     /* Ignore blank lines and lines starting with '#'. */
00122     if (line.empty() || line[line.find_first_not_of(" \t")] == '#')
00123       continue;
00124 
00125     string username;
00126     string password;
00127     size_t password_offset = line.find(":");
00128     if (password_offset == string::npos)
00129       username = line;
00130     else
00131     {
00132       username = string(line, 0, password_offset);
00133       password = string(line, password_offset + 1);
00134     }
00135 
00136     if (not users.insert(pair<string, string>(username, password)).second)
00137     {
00138       error = "Duplicate entry found in users file: " + username;
00139       return false;
00140     }
00141   }
00142   return true;
00143 }
00144 
00145 bool AuthFile::verifyMySQLHash(const string &password,
00146                                const string &scramble_bytes,
00147                                const string &scrambled_password)
00148 {
00149   if (scramble_bytes.size() != SHA1_DIGEST_LENGTH || scrambled_password.size() != SHA1_DIGEST_LENGTH)
00150   {
00151     return false;
00152   }
00153 
00154   SHA1_CTX ctx;
00155   uint8_t local_scrambled_password[SHA1_DIGEST_LENGTH];
00156   uint8_t temp_hash[SHA1_DIGEST_LENGTH];
00157   uint8_t scrambled_password_check[SHA1_DIGEST_LENGTH];
00158 
00159   /* Generate the double SHA1 hash for the password stored locally first. */
00160   SHA1Init(&ctx);
00161   SHA1Update(&ctx, reinterpret_cast<const uint8_t *>(password.c_str()), password.size());
00162   SHA1Final(temp_hash, &ctx);
00163 
00164   SHA1Init(&ctx);
00165   SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
00166   SHA1Final(local_scrambled_password, &ctx);
00167 
00168   /* Hash the scramble that was sent to client with the local password. */
00169   SHA1Init(&ctx);
00170   SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()), SHA1_DIGEST_LENGTH);
00171   SHA1Update(&ctx, local_scrambled_password, SHA1_DIGEST_LENGTH);
00172   SHA1Final(temp_hash, &ctx);
00173 
00174   /* Next, XOR the result with what the client sent to get the original
00175      single-hashed password. */
00176   for (int x= 0; x < SHA1_DIGEST_LENGTH; x++)
00177     temp_hash[x]= temp_hash[x] ^ scrambled_password[x];
00178 
00179   /* Hash this result once more to get the double-hashed password again. */
00180   SHA1Init(&ctx);
00181   SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
00182   SHA1Final(scrambled_password_check, &ctx);
00183 
00184   /* These should match for a successful auth. */
00185   return memcmp(local_scrambled_password, scrambled_password_check, SHA1_DIGEST_LENGTH) == 0;
00186 }
00187 
00188 bool AuthFile::authenticate(const identifier::User &sctx, const string &password)
00189 {
00190   string* user= find_ptr(users, sctx.username());
00191   if (not user)
00192     return false;
00193   return sctx.getPasswordType() == identifier::User::MYSQL_HASH
00194     ? verifyMySQLHash(*user, sctx.getPasswordContext(), password)
00195     : password == *user;
00196 }
00197 
00198 static int init(module::Context &context)
00199 {
00200   const module::option_map &vm= context.getOptions();
00201 
00202   AuthFile *auth_file = new AuthFile(fs::path(vm["users"].as<string>()));
00203   if (not auth_file->loadFile())
00204   {
00205     errmsg_printf(error::ERROR, _("Could not load auth file: %s\n"), auth_file->getError().c_str());
00206     delete auth_file;
00207     return 1;
00208   }
00209 
00210   context.add(auth_file);
00211   context.registerVariable(new sys_var_const_string_val("users", vm["users"].as<string>()));
00212 
00213   return 0;
00214 }
00215 
00216 
00217 static void init_options(drizzled::module::option_context &context)
00218 {
00219   context("users", 
00220           po::value<string>()->default_value(DEFAULT_USERS_FILE.string()),
00221           N_("File to load for usernames and passwords"));
00222 }
00223 
00224 } /* namespace auth_file */
00225 
00226 DRIZZLE_PLUGIN(auth_file::init, NULL, auth_file::init_options);