Drizzled Public API Documentation

auth_file.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 #include <fstream>
23 #include <map>
24 #include <string>
25 #include <iostream>
26 
27 #include <boost/program_options.hpp>
28 #include <boost/filesystem.hpp>
29 
30 #include <drizzled/configmake.h>
31 #include <drizzled/plugin/authentication.h>
32 #include <drizzled/identifier.h>
33 #include <drizzled/util/convert.h>
36 
37 namespace po= boost::program_options;
38 namespace fs= boost::filesystem;
39 
40 using namespace std;
41 using namespace drizzled;
42 
43 namespace auth_file {
44 
45 static const fs::path DEFAULT_USERS_FILE= SYSCONFDIR "/drizzle.users";
46 
47 class AuthFile : public plugin::Authentication
48 {
49 public:
50  AuthFile(fs::path users_file_arg);
51 
55  const string& getError() const;
56 
63  bool loadFile();
64 
65 private:
66 
70  bool authenticate(const identifier::User &sctx, const string &password);
71 
83  bool verifyMySQLHash(const string &password,
84  const string &scramble_bytes,
85  const string &scrambled_password);
86 
87  string error;
88  const fs::path users_file;
89 
93  typedef std::map<string, string> users_t;
94  users_t users;
95 };
96 
97 AuthFile::AuthFile(fs::path users_file_arg) :
98  plugin::Authentication("auth_file"),
99  users_file(users_file_arg)
100 {
101 }
102 
103 const string& AuthFile::getError() const
104 {
105  return error;
106 }
107 
109 {
110  ifstream file(users_file.string().c_str());
111 
112  if (!file.is_open())
113  {
114  error = "Could not open users file: " + users_file.string();
115  return false;
116  }
117 
118  string line;
119  while (getline(file, line))
120  {
121  /* Ignore blank lines and lines starting with '#'. */
122  if (line.empty() || line[line.find_first_not_of(" \t")] == '#')
123  continue;
124 
125  string username;
126  string password;
127  size_t password_offset = line.find(":");
128  if (password_offset == string::npos)
129  username = line;
130  else
131  {
132  username = string(line, 0, password_offset);
133  password = string(line, password_offset + 1);
134  }
135 
136  if (not users.insert(pair<string, string>(username, password)).second)
137  {
138  error = "Duplicate entry found in users file: " + username;
139  return false;
140  }
141  }
142  return true;
143 }
144 
145 bool AuthFile::verifyMySQLHash(const string &password,
146  const string &scramble_bytes,
147  const string &scrambled_password)
148 {
149  if (scramble_bytes.size() != SHA1_DIGEST_LENGTH || scrambled_password.size() != SHA1_DIGEST_LENGTH)
150  {
151  return false;
152  }
153 
154  SHA1_CTX ctx;
155  uint8_t local_scrambled_password[SHA1_DIGEST_LENGTH];
156  uint8_t temp_hash[SHA1_DIGEST_LENGTH];
157  uint8_t scrambled_password_check[SHA1_DIGEST_LENGTH];
158 
159  /* Generate the double SHA1 hash for the password stored locally first. */
160  SHA1Init(&ctx);
161  SHA1Update(&ctx, reinterpret_cast<const uint8_t *>(password.c_str()), password.size());
162  SHA1Final(temp_hash, &ctx);
163 
164  SHA1Init(&ctx);
165  SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
166  SHA1Final(local_scrambled_password, &ctx);
167 
168  /* Hash the scramble that was sent to client with the local password. */
169  SHA1Init(&ctx);
170  SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()), SHA1_DIGEST_LENGTH);
171  SHA1Update(&ctx, local_scrambled_password, SHA1_DIGEST_LENGTH);
172  SHA1Final(temp_hash, &ctx);
173 
174  /* Next, XOR the result with what the client sent to get the original
175  single-hashed password. */
176  for (int x= 0; x < SHA1_DIGEST_LENGTH; x++)
177  temp_hash[x]= temp_hash[x] ^ scrambled_password[x];
178 
179  /* Hash this result once more to get the double-hashed password again. */
180  SHA1Init(&ctx);
181  SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
182  SHA1Final(scrambled_password_check, &ctx);
183 
184  /* These should match for a successful auth. */
185  return memcmp(local_scrambled_password, scrambled_password_check, SHA1_DIGEST_LENGTH) == 0;
186 }
187 
188 bool AuthFile::authenticate(const identifier::User &sctx, const string &password)
189 {
190  string* user= find_ptr(users, sctx.username());
191  if (not user)
192  return false;
193  return sctx.getPasswordType() == identifier::User::MYSQL_HASH
194  ? verifyMySQLHash(*user, sctx.getPasswordContext(), password)
195  : password == *user;
196 }
197 
198 static int init(module::Context &context)
199 {
200  const module::option_map &vm= context.getOptions();
201 
202  AuthFile *auth_file = new AuthFile(fs::path(vm["users"].as<string>()));
203  if (not auth_file->loadFile())
204  {
205  errmsg_printf(error::ERROR, _("Could not load auth file: %s\n"), auth_file->getError().c_str());
206  delete auth_file;
207  return 1;
208  }
209 
210  context.add(auth_file);
211  context.registerVariable(new sys_var_const_string_val("users", vm["users"].as<string>()));
212 
213  return 0;
214 }
215 
216 
217 static void init_options(drizzled::module::option_context &context)
218 {
219  context("users",
220  po::value<string>()->default_value(DEFAULT_USERS_FILE.string()),
221  N_("File to load for usernames and passwords"));
222 }
223 
224 } /* namespace auth_file */
225 
226 DRIZZLE_DECLARE_PLUGIN
227 {
228  DRIZZLE_VERSION_ID,
229  "auth_file",
230  "0.1",
231  "Eric Day",
232  N_("Authentication against a plain text file"),
233  PLUGIN_LICENSE_GPL,
234  auth_file::init,
235  NULL,
236  auth_file::init_options
237 }
238 DRIZZLE_DECLARE_PLUGIN_END;