Drizzled Public API Documentation

auth_schema.cc
00001 /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
00002  *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
00003  *
00004  *  Copyright 2011 Daniel Nichter
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 #include <drizzled/identifier.h>
00022 #include <drizzled/util/convert.h>
00023 #include <drizzled/algorithm/sha1.h>
00024 #include <drizzled/execute.h>
00025 #include <drizzled/sql/result_set.h>
00026 #include <drizzled/plugin/listen.h>
00027 #include <drizzled/plugin/client.h>
00028 #include <drizzled/catalog/local.h>
00029 #include "auth_schema.h"
00030 
00031 namespace drizzle_plugin {
00032 namespace auth_schema {
00033 
00034 AuthSchema::AuthSchema(bool enabled) :
00035   plugin::Authentication("auth_schema"),
00036     sysvar_enabled(enabled)
00037 {
00038   const char *error;
00039   int erroffset;
00040   _ident_re= pcre_compile(
00041     "^`[^`]+`",   /* the pattern */
00042     0,            /* default options */
00043     &error,       /* for error message */
00044     &erroffset,   /* for error offset */
00045     NULL);        /* use default character tables */
00046 }
00047 
00048 bool AuthSchema::setTable(const string &table)
00049 {
00050   if (table.empty())
00051   {
00052     errmsg_printf(error::ERROR, _("auth_schema table cannot be an empty string"));
00053     return true;  // error
00054   }
00055 
00056   if (table.find(".") == string::npos)
00057   {
00058     errmsg_printf(error::ERROR, _("auth_schema must be schema-qualified"));
00059     return true;  // error
00060   }
00061 
00062   sysvar_table= escapeQuoteAuthTable(table);
00063 
00064   return false;  // success
00065 }
00066 
00067 bool AuthSchema::verifyMySQLPassword(const string &real_password,
00068                                      const string &scramble_bytes,
00069                                      const string &client_password)
00070 {
00071   if (scramble_bytes.size() != SHA1_DIGEST_LENGTH
00072       || client_password.size() != SHA1_DIGEST_LENGTH)
00073     return false;
00074 
00075   uint8_t real_password_hash[SHA1_DIGEST_LENGTH];
00076   drizzled_hex_to_string(
00077     reinterpret_cast<char *>(real_password_hash),
00078     real_password.c_str(),
00079     SHA1_DIGEST_LENGTH * 2);
00080 
00081   /* Hash the scramble that was sent to client with the local password. */
00082   SHA1_CTX ctx;
00083   uint8_t temp_hash[SHA1_DIGEST_LENGTH];
00084   SHA1Init(&ctx);
00085   SHA1Update(&ctx, reinterpret_cast<const uint8_t*>(scramble_bytes.c_str()), SHA1_DIGEST_LENGTH);
00086   SHA1Update(&ctx, real_password_hash, SHA1_DIGEST_LENGTH);
00087   SHA1Final(temp_hash, &ctx);
00088 
00089   /* Next, XOR the result with what the client sent to get the original
00090      single-hashed password. */
00091   for (int x= 0; x < SHA1_DIGEST_LENGTH; x++)
00092     temp_hash[x]= temp_hash[x] ^ client_password[x];
00093 
00094   /* Hash this result once more to get the double-hashed password again. */
00095   uint8_t client_password_hash[SHA1_DIGEST_LENGTH];
00096   SHA1Init(&ctx);
00097   SHA1Update(&ctx, temp_hash, SHA1_DIGEST_LENGTH);
00098   SHA1Final(client_password_hash, &ctx);
00099 
00100   /* These should match for a successful auth. */
00101   return memcmp(real_password_hash, client_password_hash, SHA1_DIGEST_LENGTH) == 0;
00102 }
00103 
00104 bool AuthSchema::authenticate(const identifier::User &sctx, const string &password)
00105 {
00106   // If plugin is disabled, deny everyone.
00107   if (not sysvar_enabled)
00108     return false;
00109 
00110   // Plugin only works with MySQL hash passwords, so client needs
00111   // to connect with --protocol mysql-plugin-auth.
00112   if (sctx.getPasswordType() != identifier::User::MYSQL_HASH)
00113     return false;
00114 
00115   // Anonymous users are not allowed.
00116   string user= escapeString(sctx.username());
00117   if (user.empty())
00118     return false;
00119 
00120   // Create an internal session for ourself the first time we're called.
00121   // I don't know why but doing this in the constructor crashes Drizzle
00122   if (not _session)
00123   {
00124     _session= Session::make_shared(plugin::Listen::getNullClient(), catalog::local());
00125     identifier::user::mptr user_id= identifier::User::make_shared();
00126     user_id->setUser("auth_schema");
00127     _session->setUser(user_id);
00128   }
00129 
00130   // Create an execute a SQL statement to select the user from the auth table.
00131   // Execute wraps the SQL to run within a transaction.
00132   string sql= "SELECT password FROM " + sysvar_table + " WHERE user='" + user + "' LIMIT 1;";
00133   Execute execute(*(_session.get()), true);
00134   sql::ResultSet result_set(1);
00135   execute.run(sql, result_set);
00136   sql::Exception exception= result_set.getException();
00137   drizzled::error_t err= exception.getErrorCode();
00138   if ((err != EE_OK) && (err != ER_EMPTY_QUERY))
00139   {
00140     errmsg_printf(error::ERROR,
00141       _("Error querying authentication schema: %s (error code %d.  Query: %s"),
00142       exception.getErrorMessage().c_str(), exception.getErrorCode(), sql.c_str());
00143     return false;
00144   }
00145 
00146   // If there's a result and it's not null, verify the password from
00147   // the client against the real password from the auth table.
00148   if (result_set.next() and not result_set.isNull(0))
00149   {
00150     string real_password= result_set.getString(0);
00151     // Return true if auth succeeds, else return false.
00152     return verifyMySQLPassword(
00153       real_password,
00154       sctx.getPasswordContext(),
00155       password);
00156   }
00157 
00158   // User doesn't exist in auth table; auth fails.
00159   return false;
00160 }
00161 
00162 string AuthSchema::escapeQuoteAuthTable(const string &table)
00163 {
00164   int pos= table.find(".");
00165   string quoted_schema= escapeQuoteIdentifier(table.substr(0, pos));
00166   string quoted_table= escapeQuoteIdentifier(table.substr(pos + 1, table.length() - pos));
00167   return quoted_schema + "." + quoted_table;
00168 }
00169 
00170 string AuthSchema::escapeQuoteIdentifier(const string &input)
00171 {
00172   if (input.empty())
00173     return "``";
00174 
00179   int match_result= pcre_exec(
00180     _ident_re, NULL, input.c_str(), input.length(), 0, 0, NULL, 0);
00181   if (match_result >= 0)
00182     return input;
00183 
00184   const char *pos= input.c_str();
00185   const char *end= input.c_str()+input.length();
00186   string ident= "`";
00187 
00188   for (; pos != end ; pos++)
00189   {
00190     switch (*pos) {
00191     case '`':
00192       ident.push_back('\\');
00193       ident.push_back('`');
00194       break;
00195     case '\\':
00196       ident.push_back('\\');
00197       ident.push_back('\\');
00198       break;
00199     default:
00200       ident.push_back(*pos);
00201       break;
00202     }
00203   }
00204 
00205   ident.push_back('`');
00206 
00207   return ident;
00208 }
00209 
00210 string AuthSchema::escapeString(const string &input)
00211 {
00212   if (input.empty())
00213     return input;
00214 
00215   const char *pos= input.c_str();
00216   const char *end= input.c_str()+input.length();
00217   string res;
00218 
00219   for (; pos != end ; pos++)
00220   {
00221     switch (*pos) {
00222     case 0:
00223       res.push_back('\\');
00224       res.push_back('0');
00225       break;
00226     case '\n':
00227       res.push_back('\\');
00228       res.push_back('n');
00229       break;
00230     case '\r':
00231       res.push_back('\\');
00232       res.push_back('r');
00233       break;
00234     case '\\':
00235       res.push_back('\\');
00236       res.push_back('\\');
00237       break;
00238     case '\'':
00239       res.push_back('\\');
00240       res.push_back('\'');
00241       break;
00242     default:
00243       res.push_back(*pos);
00244       break;
00245     }
00246   }
00247 
00248   return res;
00249 }
00250 
00251 AuthSchema::~AuthSchema()
00252 {
00253   if (_ident_re != NULL)
00254     pcre_free(_ident_re);
00255 }
00256 
00257 } /* end namespace drizzle_plugin::auth_schema */
00258 } /* end namespace drizzle_plugin */