~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to plugin/auth_schema/auth_schema.cc

  • Committer: Mark Atwood
  • Date: 2011-12-15 23:13:18 UTC
  • mfrom: (2465.3.1 rf3)
  • Revision ID: me@mark.atwood.name-20111215231318-mt2y2q4s5ydp79q3
mergeĀ lp:~olafvdspek/drizzle/refactor14

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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>
 
23
#include <drizzled/algorithm/sha1.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
 
 
175
  /**
 
176
   * The input may already be a quoted ident with no extra backticks.
 
177
   * If so, return it.
 
178
   */
 
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 */