Drizzled Public API Documentation

auth_schema.cc
1 /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2  * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3  *
4  * Copyright 2011 Daniel Nichter
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 #include <drizzled/identifier.h>
22 #include <drizzled/util/convert.h>
24 #include <drizzled/execute.h>
25 #include <drizzled/sql/result_set.h>
26 #include <drizzled/plugin/listen.h>
27 #include <drizzled/plugin/client.h>
28 #include <drizzled/catalog/local.h>
29 #include "auth_schema.h"
30 
31 namespace drizzle_plugin {
32 namespace auth_schema {
33 
34 AuthSchema::AuthSchema(bool enabled) :
35  plugin::Authentication("auth_schema"),
36  sysvar_enabled(enabled)
37 {
38  const char *error;
39  int erroffset;
40  _ident_re= pcre_compile(
41  "^`[^`]+`", /* the pattern */
42  0, /* default options */
43  &error, /* for error message */
44  &erroffset, /* for error offset */
45  NULL); /* use default character tables */
46 }
47 
48 bool AuthSchema::setTable(const string &table)
49 {
50  if (table.empty())
51  {
52  errmsg_printf(error::ERROR, _("auth_schema table cannot be an empty string"));
53  return true; // error
54  }
55 
56  if (table.find(".") == string::npos)
57  {
58  errmsg_printf(error::ERROR, _("auth_schema must be schema-qualified"));
59  return true; // error
60  }
61 
62  sysvar_table= escapeQuoteAuthTable(table);
63 
64  return false; // success
65 }
66 
67 bool AuthSchema::verifyMySQLPassword(const string &real_password,
68  const string &scramble_bytes,
69  const string &client_password)
70 {
71  if (scramble_bytes.size() != SHA1_DIGEST_LENGTH
72  || client_password.size() != SHA1_DIGEST_LENGTH)
73  return false;
74 
75  uint8_t real_password_hash[SHA1_DIGEST_LENGTH];
76  drizzled_hex_to_string(
77  reinterpret_cast<char *>(real_password_hash),
78  real_password.c_str(),
79  SHA1_DIGEST_LENGTH * 2);
80 
81  /* Hash the scramble that was sent to client with the local password. */
82  SHA1_CTX ctx;
83  uint8_t temp_hash[SHA1_DIGEST_LENGTH];
84  SHA1Init(&ctx);
85  SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()), SHA1_DIGEST_LENGTH);
86  SHA1Update(&ctx, real_password_hash, SHA1_DIGEST_LENGTH);
87  SHA1Final(temp_hash, &ctx);
88 
89  /* Next, XOR the result with what the client sent to get the original
90  single-hashed password. */
91  for (int x= 0; x < SHA1_DIGEST_LENGTH; x++)
92  temp_hash[x]= temp_hash[x] ^ client_password[x];
93 
94  /* Hash this result once more to get the double-hashed password again. */
95  uint8_t client_password_hash[SHA1_DIGEST_LENGTH];
96  SHA1Init(&ctx);
97  SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
98  SHA1Final(client_password_hash, &ctx);
99 
100  /* These should match for a successful auth. */
101  return memcmp(real_password_hash, client_password_hash, SHA1_DIGEST_LENGTH) == 0;
102 }
103 
104 bool AuthSchema::authenticate(const identifier::User &sctx, const string &password)
105 {
106  // If plugin is disabled, deny everyone.
107  if (not sysvar_enabled)
108  return false;
109 
110  // Plugin only works with MySQL hash passwords, so client needs
111  // to connect with --protocol mysql-plugin-auth.
112  if (sctx.getPasswordType() != identifier::User::MYSQL_HASH)
113  return false;
114 
115  // Anonymous users are not allowed.
116  string user= escapeString(sctx.username());
117  if (user.empty())
118  return false;
119 
120  // Create an internal session for ourself the first time we're called.
121  // I don't know why but doing this in the constructor crashes Drizzle
122  if (not _session)
123  {
124  _session= Session::make_shared(plugin::Listen::getNullClient(), catalog::local());
125  identifier::user::mptr user_id= identifier::User::make_shared();
126  user_id->setUser("auth_schema");
127  _session->setUser(user_id);
128  }
129 
130  // Create an execute a SQL statement to select the user from the auth table.
131  // Execute wraps the SQL to run within a transaction.
132  string sql= "SELECT password FROM " + sysvar_table + " WHERE user='" + user + "' LIMIT 1;";
133  Execute execute(*(_session.get()), true);
134  sql::ResultSet result_set(1);
135  execute.run(sql, result_set);
136  sql::Exception exception= result_set.getException();
137  drizzled::error_t err= exception.getErrorCode();
138  if ((err != EE_OK) && (err != ER_EMPTY_QUERY))
139  {
140  errmsg_printf(error::ERROR,
141  _("Error querying authentication schema: %s (error code %d. Query: %s"),
142  exception.getErrorMessage().c_str(), exception.getErrorCode(), sql.c_str());
143  return false;
144  }
145 
146  // If there's a result and it's not null, verify the password from
147  // the client against the real password from the auth table.
148  if (result_set.next() and not result_set.isNull(0))
149  {
150  string real_password= result_set.getString(0);
151  // Return true if auth succeeds, else return false.
152  return verifyMySQLPassword(
153  real_password,
154  sctx.getPasswordContext(),
155  password);
156  }
157 
158  // User doesn't exist in auth table; auth fails.
159  return false;
160 }
161 
162 string AuthSchema::escapeQuoteAuthTable(const string &table)
163 {
164  int pos= table.find(".");
165  string quoted_schema= escapeQuoteIdentifier(table.substr(0, pos));
166  string quoted_table= escapeQuoteIdentifier(table.substr(pos + 1, table.length() - pos));
167  return quoted_schema + "." + quoted_table;
168 }
169 
170 string AuthSchema::escapeQuoteIdentifier(const string &input)
171 {
172  if (input.empty())
173  return "``";
174 
179  int match_result= pcre_exec(
180  _ident_re, NULL, input.c_str(), input.length(), 0, 0, NULL, 0);
181  if (match_result >= 0)
182  return input;
183 
184  const char *pos= input.c_str();
185  const char *end= input.c_str()+input.length();
186  string ident= "`";
187 
188  for (; pos != end ; pos++)
189  {
190  switch (*pos) {
191  case '`':
192  ident.push_back('\\');
193  ident.push_back('`');
194  break;
195  case '\\':
196  ident.push_back('\\');
197  ident.push_back('\\');
198  break;
199  default:
200  ident.push_back(*pos);
201  break;
202  }
203  }
204 
205  ident.push_back('`');
206 
207  return ident;
208 }
209 
210 string AuthSchema::escapeString(const string &input)
211 {
212  if (input.empty())
213  return input;
214 
215  const char *pos= input.c_str();
216  const char *end= input.c_str()+input.length();
217  string res;
218 
219  for (; pos != end ; pos++)
220  {
221  switch (*pos) {
222  case 0:
223  res.push_back('\\');
224  res.push_back('0');
225  break;
226  case '\n':
227  res.push_back('\\');
228  res.push_back('n');
229  break;
230  case '\r':
231  res.push_back('\\');
232  res.push_back('r');
233  break;
234  case '\\':
235  res.push_back('\\');
236  res.push_back('\\');
237  break;
238  case '\'':
239  res.push_back('\\');
240  res.push_back('\'');
241  break;
242  default:
243  res.push_back(*pos);
244  break;
245  }
246  }
247 
248  return res;
249 }
250 
251 AuthSchema::~AuthSchema()
252 {
253  if (_ident_re != NULL)
254  pcre_free(_ident_re);
255 }
256 
257 } /* end namespace drizzle_plugin::auth_schema */
258 } /* end namespace drizzle_plugin */