From: Rafal Somla Date: March 28 2011 4:21pm Subject: bzr commit into mysql-5.5 branch (rafal.somla:3374) Bug#11766631 List-Archive: http://lists.mysql.com/commits/134091 X-Bug: 11766631 Message-Id: <201103281620.p2SGK2HG015683@acsmt356.oracle.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============1834032338==" --===============1834032338== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At D:/source/bzr2/mysql-5.5 based on revid:build@stripped 3374 Rafal Somla 2011-03-28 Bug#11766631 (59780) - Move the client authentication_windows plugin into the server repository This patch adds client windows authentication plugin code to the client library libmysql (only on Windows platform). The plugin is compiled into the library and added to the list of built-in plugins. This way clients should be able to connect to a server which uses windows authentication plugin even as an SQL user which uses such authentication. Note: this makes the client library to depend on Secur32 Windows system library. When building clients, they must be linked against Secur32. Command mysql_config --libs correctly lists Secur32 as a required dependency. added: libmysql/authentication_win/ libmysql/authentication_win/CMakeLists.txt libmysql/authentication_win/common.cc libmysql/authentication_win/common.h libmysql/authentication_win/handshake.cc libmysql/authentication_win/handshake.h libmysql/authentication_win/handshake_client.cc libmysql/authentication_win/log_client.cc libmysql/authentication_win/plugin_client.cc modified: libmysql/CMakeLists.txt sql-common/client.c === modified file 'libmysql/CMakeLists.txt' --- a/libmysql/CMakeLists.txt 2010-11-13 22:16:52 +0000 +++ b/libmysql/CMakeLists.txt 2011-03-28 16:20:59 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -134,6 +134,12 @@ CACHE INTERNAL "Functions exported by cl ) +IF(WIN32) + ADD_SUBDIRECTORY(authentication_win) + SET(WITH_AUTHENTICATION_WIN 1) + ADD_DEFINITIONS(-DAUTHENTICATION_WIN) +ENDIF(WIN32) + SET(CLIENT_SOURCES get_password.c libmysql.c @@ -150,6 +156,10 @@ DTRACE_INSTRUMENT(clientlib) ADD_DEPENDENCIES(clientlib GenError) SET(LIBS clientlib dbug strings vio mysys ${ZLIB_LIBRARY} ${SSL_LIBRARIES} ${LIBDL}) + +IF(WITH_AUTHENTICATION_WIN) + LIST(APPEND LIBS auth_win_client) +ENDIF(WITH_AUTHENTICATION_WIN) # Merge several convenience libraries into one big mysqlclient # and link them together into shared library. === added directory 'libmysql/authentication_win' === added file 'libmysql/authentication_win/CMakeLists.txt' --- a/libmysql/authentication_win/CMakeLists.txt 1970-01-01 00:00:00 +0000 +++ b/libmysql/authentication_win/CMakeLists.txt 2011-03-28 16:20:59 +0000 @@ -0,0 +1,31 @@ +# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# +# Configuration for building Windows Authentication Plugin (client-side) +# + +ADD_DEFINITIONS(-DSECURITY_WIN32) +ADD_DEFINITIONS(-DDEBUG_ERRROR_LOG) # no error logging in production builds + +SET(HEADERS common.h handshake.h) +SET(PLUGIN_SOURCES plugin_client.cc handshake_client.cc log_client.cc common.cc handshake.cc) + +ADD_CONVENIENCE_LIBRARY(auth_win_client ${PLUGIN_SOURCES} ${HEADERS}) +TARGET_LINK_LIBRARIES(auth_win_client Secur32) + +# In IDE, group headers in a separate folder. + +SOURCE_GROUP(Headers REGULAR_EXPRESSION ".*h$") === added file 'libmysql/authentication_win/common.cc' --- a/libmysql/authentication_win/common.cc 1970-01-01 00:00:00 +0000 +++ b/libmysql/authentication_win/common.cc 2011-03-28 16:20:59 +0000 @@ -0,0 +1,492 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "common.h" +#include // for ConvertSidToStringSid() +#include // for GetUserNameEx() + + +template <> void error_log_print(const char *fmt, ...); +template <> void error_log_print(const char *fmt, ...); +template <> void error_log_print(const char *fmt, ...); + + +/** Connection class **************************************************/ + +/** + Create connection out of an active MYSQL_PLUGIN_VIO object. + + @param[in] vio pointer to a @c MYSQL_PLUGIN_VIO object used for + connection - it can not be NULL +*/ + +Connection::Connection(MYSQL_PLUGIN_VIO *vio): m_vio(vio), m_error(0) +{ + DBUG_ASSERT(vio); +} + + +/** + Write data to the connection. + + @param[in] blob data to be written + + @return 0 on success, VIO error code on failure. + + @note In case of error, VIO error code is stored in the connection object + and can be obtained with @c error() method. +*/ + +int Connection::write(const Blob &blob) +{ + m_error= m_vio->write_packet(m_vio, blob.ptr(), blob.len()); + +#ifndef DBUG_OFF + if (m_error) + DBUG_PRINT("error", ("vio write error %d", m_error)); +#endif + + return m_error; +} + + +/** + Read data from connection. + + @return A Blob containing read packet or null Blob in case of error. + + @note In case of error, VIO error code is stored in the connection object + and can be obtained with @c error() method. +*/ + +Blob Connection::read() +{ + unsigned char *ptr; + int len= m_vio->read_packet(m_vio, &ptr); + + if (len < 0) + { + m_error= true; + return Blob(); + } + + return Blob(ptr, len); +} + + +/** Sid class *****************************************************/ + + +/** + Create Sid object corresponding to a given account name. + + @param[in] account_name name of a Windows account + + The account name can be in any form accepted by @c LookupAccountName() + function. + + @note In case of errors created object is invalid and its @c is_valid() + method returns @c false. +*/ + +Sid::Sid(const wchar_t *account_name): m_data(NULL) +#ifndef DBUG_OFF +, m_as_string(NULL) +#endif +{ + DWORD sid_size= 0, domain_size= 0; + bool success; + + // Determine required buffer sizes + + success= LookupAccountNameW(NULL, account_name, NULL, &sid_size, + NULL, &domain_size, &m_type); + + if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not determine SID buffer size, " + "LookupAccountName() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + return; + } + + // Query for SID (domain is ignored) + + wchar_t *domain= new wchar_t[domain_size]; + m_data= (TOKEN_USER*) new BYTE[sid_size + sizeof(TOKEN_USER)]; + m_data->User.Sid= (BYTE*)m_data + sizeof(TOKEN_USER); + + success= LookupAccountNameW(NULL, account_name, + m_data->User.Sid, &sid_size, + domain, &domain_size, + &m_type); + + if (!success || !is_valid()) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not determine SID of '%S', " + "LookupAccountName() failed with error %X (%s)", + account_name, GetLastError(), + get_last_error_message(error_buf))); +#endif + goto fail; + } + + goto end; + +fail: + if (m_data) + delete [] m_data; + m_data= NULL; + +end: + if (domain) + delete [] domain; +} + + +/** + Create Sid object corresponding to a given security token. + + @param[in] token security token of a Windows account + + @note In case of errors created object is invalid and its @c is_valid() + method returns @c false. +*/ + +Sid::Sid(HANDLE token): m_data(NULL) +#ifndef DBUG_OFF +, m_as_string(NULL) +#endif +{ + DWORD req_size= 0; + bool success; + + // Determine required buffer size + + success= GetTokenInformation(token, TokenUser, NULL, 0, &req_size); + if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not determine SID buffer size, " + "GetTokenInformation() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + return; + } + + m_data= (TOKEN_USER*) new BYTE[req_size]; + success= GetTokenInformation(token, TokenUser, m_data, req_size, &req_size); + + if (!success || !is_valid()) + { + delete [] m_data; + m_data= NULL; +#ifndef DBUG_OFF + if (!success) + { + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not read SID from security token, " + "GetTokenInformation() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); + } +#endif + } +} + + +Sid::~Sid() +{ + if (m_data) + delete [] m_data; +#ifndef DBUG_OFF + if (m_as_string) + LocalFree(m_as_string); +#endif +} + +/// Check if Sid object is valid. +bool Sid::is_valid(void) const +{ + return m_data && m_data->User.Sid && IsValidSid(m_data->User.Sid); +} + + +#ifndef DBUG_OFF + +/** + Produces string representation of the SID. + + @return String representation of the SID or NULL in case of errors. + + @note Memory allocated for the string is automatically freed in Sid's + destructor. +*/ + +const char* Sid::as_string() +{ + if (!m_data) + return NULL; + + if (!m_as_string) + { + bool success= ConvertSidToStringSid(m_data->User.Sid, &m_as_string); + + if (!success) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not get textual representation of a SID, " + "ConvertSidToStringSid() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + m_as_string= NULL; + return NULL; + } + } + + return m_as_string; +} + +#endif + + +bool Sid::operator ==(const Sid &other) +{ + if (!is_valid() || !other.is_valid()) + return false; + + return EqualSid(m_data->User.Sid, other.m_data->User.Sid); +} + + +/** Generating User Principal Name *************************/ + +/** + Call Windows API functions to get UPN of the current user and store it + in internal buffer. +*/ + +UPN::UPN(): m_buf(NULL) +{ + wchar_t buf1[MAX_SERVICE_NAME_LENGTH]; + + // First we try to use GetUserNameEx. + + m_len= sizeof(buf1)/sizeof(wchar_t); + + if (!GetUserNameExW(NameUserPrincipal, buf1, (PULONG)&m_len)) + { + if (GetLastError()) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("note", ("When determining UPN" + ", GetUserNameEx() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + if (ERROR_MORE_DATA == GetLastError()) + ERROR_LOG(INFO, ("Buffer overrun when determining UPN:" + " need %ul characters but have %ul", + m_len, sizeof(buf1)/sizeof(WCHAR))); + } + + m_len= 0; // m_len == 0 indicates invalid UPN + return; + } + + /* + UPN is stored in buf1 in wide-char format - convert it to utf8 + for sending over network. + */ + + m_buf= wchar_to_utf8(buf1, &m_len); + + if(!m_buf) + ERROR_LOG(ERROR, ("Failed to convert UPN to utf8")); + + // Note: possible error would be indicated by the fact that m_buf is NULL. + return; +} + + +UPN::~UPN() +{ + if (m_buf) + free(m_buf); +} + + +/** + Convert a wide-char string to utf8 representation. + + @param[in] string null-terminated wide-char string to be converted + @param[in,out] len length of the string to be converted or 0; on + return length (in bytes, excluding terminating + null character) of the converted string + + If len is 0 then the length of the string will be computed by this function. + + @return Pointer to a buffer containing utf8 representation or NULL in + case of error. + + @note The returned buffer must be freed with @c free() call. +*/ + +char* wchar_to_utf8(const wchar_t *string, size_t *len) +{ + char *buf= NULL; + size_t str_len= len && *len ? *len : wcslen(string); + + /* + A conversion from utf8 to wchar_t will never take more than 3 bytes per + character, so a buffer of length 3 * str_len schould be sufficient. + We check that assumption with an assertion later. + */ + + size_t buf_len= 3 * str_len; + + buf= (char*)malloc(buf_len + 1); + if (!buf) + { + DBUG_PRINT("error",("Out of memory when converting string '%S' to utf8", + string)); + return NULL; + } + + int res= WideCharToMultiByte(CP_UTF8, // convert to UTF-8 + 0, // conversion flags + string, // input buffer + str_len, // its length + buf, buf_len, // output buffer and its size + NULL, NULL); // default character (not used) + + if (res) + { + buf[res]= '\0'; + if (len) + *len= res; + return buf; + } + + // res is 0 which indicates error + +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not convert string '%S' to utf8" + ", WideCharToMultiByte() failed with error %X (%s)", + string, GetLastError(), + get_last_error_message(error_buf))); +#endif + + // Let's check our assumption about sufficient buffer size + DBUG_ASSERT(ERROR_INSUFFICIENT_BUFFER != GetLastError()); + + return NULL; +} + + +/** + Convert an utf8 string to a wide-char string. + + @param[in] string null-terminated utf8 string to be converted + @param[in,out] len length of the string to be converted or 0; on + return length (in chars) of the converted string + + If len is 0 then the length of the string will be computed by this function. + + @return Pointer to a buffer containing wide-char representation or NULL in + case of error. + + @note The returned buffer must be freed with @c free() call. +*/ + +wchar_t* utf8_to_wchar(const char *string, size_t *len) +{ + size_t buf_len; + + /* + Note: length (in bytes) of an utf8 string is always bigger than the + number of characters in this string. Hence a buffer of size len will + be sufficient. We add 1 for the terminating null character. + */ + + buf_len= len && *len ? *len : strlen(string); + wchar_t *buf= (wchar_t*)malloc((buf_len+1)*sizeof(wchar_t)); + + if (!buf) + { + DBUG_PRINT("error",("Out of memory when converting utf8 string '%s'" + " to wide-char representation", string)); + return NULL; + } + + size_t res; + res= MultiByteToWideChar(CP_UTF8, // convert from UTF-8 + 0, // conversion flags + string, // input buffer + buf_len, // its size + buf, buf_len); // output buffer and its size + if (res) + { + buf[res]= '\0'; + if (len) + *len= res; + return buf; + } + + // error in MultiByteToWideChar() + +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not convert UPN from UTF-8" + ", MultiByteToWideChar() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + + // Let's check our assumption about sufficient buffer size + DBUG_ASSERT(ERROR_INSUFFICIENT_BUFFER != GetLastError()); + + return NULL; +} + + +/** Error handling ****************************************************/ + + +/** + Returns error message corresponding to the last Windows error given + by GetLastError(). + + @note Error message is overwritten by next call to + @c get_last_error_message(). +*/ + +const char* get_last_error_message(Error_message_buf buf) +{ + int error= GetLastError(); + + buf[0]= '\0'; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)buf, sizeof(buf), NULL ); + + return buf; +} === added file 'libmysql/authentication_win/common.h' --- a/libmysql/authentication_win/common.h 1970-01-01 00:00:00 +0000 +++ b/libmysql/authentication_win/common.h 2011-03-28 16:20:59 +0000 @@ -0,0 +1,290 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include // for CtxtHandle +#include // for MYSQL_PLUGIN_VIO + +/// Maximum length of the target service name. +#define MAX_SERVICE_NAME_LENGTH 1024 + + +/** Debugging and error reporting infrastructure ***************************/ + +/* + Note: We use plugin local logging and error reporting mechanisms until + WL#2940 (plugin service: error reporting) is available. +*/ + +#undef INFO +#undef WARNING +#undef ERROR + +struct error_log_level +{ + typedef enum {INFO, WARNING, ERROR} type; +}; + +#undef DBUG_ASSERT +#ifndef DBUG_OFF +#define DBUG_ASSERT(X) assert(X) +#else +#define DBUG_ASSERT(X) do {} while (0) +#endif + +extern "C" int opt_auth_win_client_log; + +/* + Note1: Double level of indirection in definition of DBUG_PRINT allows to + temporary redefine or disable DBUG_PRINT macro and then easily return to + the original definition (in terms of DBUG_PRINT_DO). + + Note2: DBUG_PRINT() can use printf-like format string like this: + + DBUG_PRINT(Keyword, ("format string", args)); + + The implementation should handle it correctly. Currently it is passed + to fprintf() (see debug_msg() function). +*/ + +#ifndef DBUG_OFF +#define DBUG_PRINT_DO(Keyword, Msg) \ + do { \ + if (2 > opt_auth_win_client_log) break; \ + fprintf(stderr, "winauth: %s: ", Keyword); \ + debug_msg Msg; \ + } while (0) +#else +#define DBUG_PRINT_DO(K, M) do {} while (0) +#endif + +#undef DBUG_PRINT +#define DBUG_PRINT(Keyword, Msg) DBUG_PRINT_DO(Keyword, Msg) + +/* + If DEBUG_ERROR_LOG is defined then error logging happens only + in debug-copiled code. Otherwise ERROR_LOG() expands to + error_log_print() even in production code. Note that in client + plugin, error_log_print() will print nothing if opt_auth_win_clinet_log + is 0. +*/ +#if defined(DEBUG_ERROR_LOG) && defined(DBUG_OFF) +#define ERROR_LOG(Level, Msg) do {} while (0) +#else +#define ERROR_LOG(Level, Msg) error_log_print< error_log_level::Level > Msg +#endif + +inline +void debug_msg(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + fflush(stderr); + va_end(args); +} + + +void error_log_vprint(error_log_level::type level, + const char *fmt, va_list args); + +template +void error_log_print(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + error_log_vprint(Level, fmt, args); + va_end(args); +} + +typedef char Error_message_buf[1024]; +const char* get_last_error_message(Error_message_buf); + + +/** Blob class *************************************************************/ + +typedef unsigned char byte; + +/** + Class representing a region of memory (e.g., a string or binary buffer). + + @note This class does not allocate memory. It merely describes a region + of memory which must be allocated externally (if it is dynamic memory). +*/ + +class Blob +{ + byte *m_ptr; ///< Pointer to the first byte of the memory region. + size_t m_len; ///< Length of the memory region. + +public: + + Blob(): m_ptr(NULL), m_len(0) + {} + + Blob(const byte *ptr, const size_t len) + : m_ptr(const_cast(ptr)), m_len(len) + {} + + Blob(const char *str): m_ptr((byte*)str) + { + m_len= strlen(str); + } + + byte* ptr() const + { + return m_ptr; + } + + size_t len() const + { + return m_len; + } + + byte operator[](unsigned pos) const + { + return pos < len() ? m_ptr[pos] : 0x00; + } + + bool is_null() const + { + return m_ptr == NULL; + } +}; + + +/** Connection class *******************************************************/ + +/** + Convenience wrapper around MYSQL_PLUGIN_VIO object providing basic + read/write operations. +*/ + +class Connection +{ + MYSQL_PLUGIN_VIO *m_vio; ///< Pointer to @c MYSQL_PLUGIN_VIO structure. + + /** + If non-zero, indicates that connection is broken. If this has happened + because of failed operation, stores non-zero error code from that failure. + */ + int m_error; + +public: + + Connection(MYSQL_PLUGIN_VIO *vio); + int write(const Blob&); + Blob read(); + + int error() const + { + return m_error; + } +}; + + +/** Sid class **************************************************************/ + +/** + Class for storing and manipulating Windows security identifiers (SIDs). +*/ + +class Sid +{ + TOKEN_USER *m_data; ///< Pointer to structure holding identifier's data. + SID_NAME_USE m_type; ///< Type of identified entity. + +public: + + Sid(const wchar_t*); + Sid(HANDLE sec_token); + ~Sid(); + + bool is_valid(void) const; + + bool is_group(void) const + { + return m_type == SidTypeGroup + || m_type == SidTypeWellKnownGroup + || m_type == SidTypeAlias; + } + + bool is_user(void) const + { + return m_type == SidTypeUser; + } + + bool operator==(const Sid&); + + operator PSID() const + { + return (PSID)m_data->User.Sid; + } + +#ifndef DBUG_OFF + +private: + char *m_as_string; ///< Cached string representation of the SID. +public: + const char* as_string(); + +#endif +}; + + +/** UPN class **************************************************************/ + +/** + An object of this class obtains and stores User Principal Name of the + account under which current process is running. +*/ + +class UPN +{ + char *m_buf; ///< Pointer to UPN in utf8 representation. + size_t m_len; ///< Length of the name. + +public: + + UPN(); + ~UPN(); + + bool is_valid() const + { + return m_len > 0; + } + + const Blob as_blob() const + { + return m_len ? Blob((byte*)m_buf, m_len) : Blob(); + } + + const char* as_string() const + { + return (const char*)m_buf; + } + +}; + + +char* wchar_to_utf8(const wchar_t*, size_t*); +wchar_t* utf8_to_wchar(const char*, size_t*); + +#endif === added file 'libmysql/authentication_win/handshake.cc' --- a/libmysql/authentication_win/handshake.cc 1970-01-01 00:00:00 +0000 +++ b/libmysql/authentication_win/handshake.cc 2011-03-28 16:20:59 +0000 @@ -0,0 +1,288 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "handshake.h" + + +/** Handshake class implementation **********************************/ + +/** + Create common part of handshake context. + + @param[in] ssp name of the SSP (Security Service Provider) to + be used for authentication + @param[in] side is this handshake object used for server- or + client-side handshake + + Prepare for handshake using the @c ssp security module. We use + "Negotiate" which picks best available module. Parameter @c side + tells if this is preparing for server or client side authentication + and is used to prepare appropriate credentials. +*/ + +Handshake::Handshake(const char *ssp, side_t side) +: m_atts(0L), m_error(0), m_complete(FALSE), + m_have_credentials(false), m_have_sec_context(false) +#ifndef DBUG_OFF + , m_ssp_info(NULL) +#endif +{ + SECURITY_STATUS ret; + + // Obtain credentials for the authentication handshake. + + ret= AcquireCredentialsHandle(NULL, (SEC_CHAR*)ssp, + side == SERVER ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, + NULL, NULL, NULL, NULL, &m_cred, &m_expire); + + if (ret != SEC_E_OK) + { + DBUG_PRINT("error", ("AcqireCredentialsHandle() failed" + " with error %X", ret)); + ERROR_LOG(ERROR, ("Could not obtain local credentials" + " required for authentication")); + m_error= ret; + } + + m_have_credentials= true; +} + + +Handshake::~Handshake() +{ + if (m_have_credentials) + FreeCredentialsHandle(&m_cred); + if (m_have_sec_context) + DeleteSecurityContext(&m_sctx); + m_output.free(); + +#ifndef DBUG_OFF + if (m_ssp_info) + FreeContextBuffer(m_ssp_info); +#endif +} + + +/** + Read and process data packets from the other end of a connection. + + @param[IN] con a connection to read packets from + + Packets are read and processed until authentication handshake is + complete. It is assumed that the peer will send at least one packet. + Packets are processed with @c process_data() method. If new data is + generated during packet processing, this data is sent to the peer and + another round of packet exchange starts. + + @return 0 on success. + + @note In case of error, appropriate error message is logged. +*/ +int Handshake::packet_processing_loop(Connection &con) +{ + unsigned round= 1; + + do { + // Read packet send by the peer + DBUG_PRINT("info", ("Waiting for packet")); + Blob packet= con.read(); + if (con.error() || packet.is_null()) + { + ERROR_LOG(ERROR, ("Error reading packet in round %d", round)); + return 1; + } + DBUG_PRINT("info", ("Got packet of length %d", packet.len())); + + /* + Process received data, possibly generating new data to be sent. + */ + + Blob new_data= process_data(packet); + + if (error()) + { + ERROR_LOG(ERROR, ("Error processing packet in round %d", round)); + return 1; + } + + /* + If new data has been generated, send it to the peer. Otherwise + handshake must be completed. + */ + + if (!new_data.is_null()) + { + ++round; + DBUG_PRINT("info", ("Round %d started", round)); + + DBUG_PRINT("info", ("Sending packet of length %d", new_data.len())); + int ret= con.write(new_data); + if (ret) + { + ERROR_LOG(ERROR, ("Error writing packet in round %d", round)); + return 1; + } + DBUG_PRINT("info", ("Data sent")); + } + else if (!is_complete()) + { + ERROR_LOG(ERROR, ("No data to send in round %d" + " but handshake is not complete", round)); + return 1; + } + + /* + To protect against malicious clients, break handshake exchange if + too many rounds. + */ + + if (round > MAX_HANDSHAKE_ROUNDS) + { + ERROR_LOG(ERROR, ("Authentication handshake could not be completed" + " after %d rounds", round)); + return 1; + } + + } while(!is_complete()); + + ERROR_LOG(INFO, ("Handshake completed after %d rounds", round)); + return 0; +} + + +#ifndef DBUG_OFF + +/** + Get name of the security package which was used in authentication. + + This method should be called only after handshake was completed. It is + available only in debug builds. + + @return Name of security package or NULL if it can not be obtained. +*/ + +const char* Handshake::ssp_name() +{ + if (!m_ssp_info && m_complete) + { + SecPkgContext_PackageInfo pinfo; + + int ret= QueryContextAttributes(&m_sctx, SECPKG_ATTR_PACKAGE_INFO, &pinfo); + + if (SEC_E_OK == ret) + { + m_ssp_info= pinfo.PackageInfo; + } + else + DBUG_PRINT("error", + ("Could not obtain SSP info from authentication context" + ", QueryContextAttributes() failed with error %X", ret)); + } + + return m_ssp_info ? m_ssp_info->Name : NULL; +} + +#endif + + +/** + Process result of @c {Initialize,Accept}SecurityContext() function. + + @param[in] ret return code from @c {Initialize,Accept}SecurityContext() + function + + This function analyses return value of Windows + @c {Initialize,Accept}SecurityContext() function. A call to + @c CompleteAuthToken() is done if requested. If authentication is complete, + this fact is marked in the internal state of the Handshake object. + If errors are detected the object is moved to error state. + + @return True if error has been detected. +*/ + +bool Handshake::process_result(int ret) +{ + /* + First check for errors and set the m_complete flag if the result + indicates that handshake is complete. + */ + + switch (ret) + { + case SEC_E_OK: + case SEC_I_COMPLETE_NEEDED: + // Handshake completed + m_complete= true; + break; + + case SEC_I_CONTINUE_NEEDED: + case SEC_I_COMPLETE_AND_CONTINUE: + break; + + default: + m_error= ret; + return true; + } + + m_have_sec_context= true; + + /* + If the result indicates a need for this, complete the authentication + token. + */ + + switch (ret) + { + case SEC_I_COMPLETE_NEEDED: + case SEC_I_COMPLETE_AND_CONTINUE: + ret= CompleteAuthToken(&m_sctx, &m_output); + if (ret != 0) + { + DBUG_PRINT("error", ("CompleteAuthToken() failed with error %X", ret)); + m_error= ret; + return true; + } + default: + break; + } + + return false; +} + + +/** Security_buffer class implementation **********************************/ + + +Security_buffer::Security_buffer(const Blob &blob): m_allocated(false) +{ + init(blob.ptr(), blob.len()); +} + + +Security_buffer::Security_buffer(): m_allocated(true) +{ + init(NULL, 0); +} + + +void Security_buffer::free(void) +{ + if (!m_allocated) + return; + if (!ptr()) + return; + FreeContextBuffer(ptr()); + m_allocated= false; +} === added file 'libmysql/authentication_win/handshake.h' --- a/libmysql/authentication_win/handshake.h 1970-01-01 00:00:00 +0000 +++ b/libmysql/authentication_win/handshake.h 2011-03-28 16:20:59 +0000 @@ -0,0 +1,168 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef HANDSHAKE_H +#define HANDSHAKE_H + +#include "common.h" + +/** + Name of the SSP (Security Support Provider) to be used for authentication. + + We use "Negotiate" which will find the most secure SSP which can be used + and redirect to that SSP. +*/ +#define SSP_NAME "Negotiate" + +/** + Maximal number of rounds in authentication handshake. + + Server will interrupt authentication handshake with error if client's + identity can not be determined within this many rounds. +*/ +#define MAX_HANDSHAKE_ROUNDS 50 + + +/// Convenience wrapper around @c SecBufferDesc. + +class Security_buffer: public SecBufferDesc +{ + SecBuffer m_buf; ///< A @c SecBuffer instance. + + void init(byte *ptr, size_t len) + { + ulVersion= 0; + cBuffers= 1; + pBuffers= &m_buf; + + m_buf.BufferType= SECBUFFER_TOKEN; + m_buf.pvBuffer= ptr; + m_buf.cbBuffer= len; + } + + /// If @c false, no deallocation will be done in the destructor. + bool m_allocated; + + public: + + Security_buffer(const Blob&); + Security_buffer(); + + ~Security_buffer() + { + free(); + } + + byte* ptr() const + { + return (byte*)m_buf.pvBuffer; + } + + size_t len() const + { + return m_buf.cbBuffer; + } + + bool is_valid() const + { + return ptr() != NULL; + } + + const Blob as_blob() const + { + return Blob(ptr(), len()); + } + + void free(void); +}; + + +/// Common base for Handshake_{server,client}. + +class Handshake +{ +public: + + typedef enum {CLIENT, SERVER} side_t; + + Handshake(const char *ssp, side_t side); + virtual ~Handshake(); + + int Handshake::packet_processing_loop(Connection &con); + + bool virtual is_complete() const + { + return m_complete; + } + + int error() const + { + return m_error; + } + +protected: + + /// Security context object created during the handshake. + CtxtHandle m_sctx; + + /// Credentials of the principal performing this handshake. + CredHandle m_cred; + + /// Stores expiry date of the created security context. + TimeStamp m_expire; + + /// Stores attributes of the created security context. + ULONG m_atts; + + /// If non-zero, stores error code of the last failed operation. + int m_error; + + /// @c true when handshake is complete. + bool m_complete; + + /// @c true when the principal credentials has been determined. + bool m_have_credentials; + + /// @c true when the security context has been created. + bool m_have_sec_context; + + /// Buffer for data to be send to the other side. + Security_buffer m_output; + + bool process_result(int); + + /** + This method is used inside @c packet_processing_loop to process + data packets received from the other end. + + @param[IN] data data to be processed + + @return A blob with data to be sent to the other end or null blob if + no more data needs to be exchanged. + */ + virtual Blob process_data(const Blob &data)= 0; + +#ifndef DBUG_OFF + +private: + SecPkgInfo *m_ssp_info; +public: + const char* ssp_name(); + +#endif +}; + + +#endif === added file 'libmysql/authentication_win/handshake_client.cc' --- a/libmysql/authentication_win/handshake_client.cc 1970-01-01 00:00:00 +0000 +++ b/libmysql/authentication_win/handshake_client.cc 2011-03-28 16:20:59 +0000 @@ -0,0 +1,285 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "handshake.h" + +#include // for MYSQL structure + + +/// Client-side context for authentication handshake + +class Handshake_client: public Handshake +{ + /** + Name of the server's service for which we authenticate. + + The service name is sent by server in the initial packet. If no + service name is used, this member is @c NULL. + */ + SEC_WCHAR *m_service_name; + + /// Buffer for storing service name obtained from server. + SEC_WCHAR m_service_name_buf[MAX_SERVICE_NAME_LENGTH]; + +public: + + Handshake_client(const char *target, size_t len); + ~Handshake_client(); + + Blob first_packet(); + Blob process_data(const Blob&); +}; + + +/** + Create authentication handshake context for client. + + @param target name of the target service with which we will authenticate + (can be NULL if not used) + + Some security packages (like Kerberos) require providing explicit name + of the service with which a client wants to authenticate. The server-side + authentication plugin sends this name in the greeting packet + (see @c win_auth_handshake_{server,client}() functions). +*/ + +Handshake_client::Handshake_client(const char *target, size_t len) +: Handshake(SSP_NAME, CLIENT), m_service_name(NULL) +{ + if (!target || 0 == len) + return; + + // Convert received UPN to internal WCHAR representation. + + m_service_name= utf8_to_wchar(target, &len); + + if (m_service_name) + DBUG_PRINT("info", ("Using target service: %S\n", m_service_name)); + else + { + /* + Note: we ignore errors here - m_target will be NULL, the target name + will not be used and system will fall-back to NTLM authentication. But + we leave trace in error log. + */ + ERROR_LOG(WARNING, ("Could not decode UPN sent by the server" + "; target service name will not be used" + " and Kerberos authentication will not work")); + } +} + + +Handshake_client::~Handshake_client() +{ + if (m_service_name) + free(m_service_name); +} + + +/** + Generate first packet to be sent to the server during packet exchange. + + This first packet should contain some data. In case of error a null blob + is returned and @c error() gives non-zero error code. + + @return Data to be sent in the first packet or null blob in case of error. +*/ + +Blob Handshake_client::first_packet() +{ + SECURITY_STATUS ret; + + m_output.free(); + + ret= InitializeSecurityContextW( + &m_cred, + NULL, // partial context + m_service_name, // service name + ASC_REQ_ALLOCATE_MEMORY, // requested attributes + 0, // reserved + SECURITY_NETWORK_DREP, // data representation + NULL, // input data + 0, // reserved + &m_sctx, // context + &m_output, // output data + &m_atts, // attributes + &m_expire); // expire date + + if (process_result(ret)) + { + DBUG_PRINT("error", + ("InitializeSecurityContext() failed with error %X", ret)); + return Blob(); + } + + return m_output.as_blob(); +} + + +/** + Process data sent by server. + + @param[in] data blob with data from server + + This method analyses data sent by server during authentication handshake. + If client should continue packet exchange, this method returns data to + be sent to the server next. If no more data needs to be exchanged, an + empty blob is returned and @c is_complete() is @c true. In case of error + an empty blob is returned and @c error() gives non-zero error code. + + @return Data to be sent to the server next or null blob if no more data + needs to be exchanged or in case of error. +*/ + +Blob Handshake_client::process_data(const Blob &data) +{ + Security_buffer input(data); + SECURITY_STATUS ret; + + m_output.free(); + + ret= InitializeSecurityContextW( + &m_cred, + &m_sctx, // partial context + m_service_name, // service name + ASC_REQ_ALLOCATE_MEMORY, // requested attributes + 0, // reserved + SECURITY_NETWORK_DREP, // data representation + &input, // input data + 0, // reserved + &m_sctx, // context + &m_output, // output data + &m_atts, // attributes + &m_expire); // expire date + + if (process_result(ret)) + { + DBUG_PRINT("error", + ("InitializeSecurityContext() failed with error %X", ret)); + return Blob(); + } + + return m_output.as_blob(); +} + + +/**********************************************************************/ + + +/** + Perform authentication handshake from client side. + + @param[in] vio pointer to @c MYSQL_PLUGIN_VIO instance to be used + for communication with the server + @param[in] mysql pointer to a MySQL connection for which we authenticate + + After reading the initial packet from server, containing its UPN to be + used as service name, client starts packet exchange by sending the first + packet in this exchange. While handshake is not yet completed, client + reads packets sent by the server and process them, possibly generating new + data to be sent to the server. + + This function reports errors. + + @return 0 on success. +*/ + +int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) +{ + /* + Check if we should enable logging. + */ + { + const char *opt= getenv("AUTHENTICATION_WIN_LOG"); + int opt_val= opt ? atoi(opt) : 0; + if (opt && !opt_val) + { + if (!strncasecmp("on", opt, 2)) opt_val= 1; + if (!strncasecmp("yes", opt, 3)) opt_val= 1; + if (!strncasecmp("true", opt, 4)) opt_val= 1; + if (!strncasecmp("debug", opt, 5)) opt_val= 2; + if (!strncasecmp("dbug", opt, 4)) opt_val= 2; + } + opt_auth_win_client_log= opt_val; + } + + ERROR_LOG(INFO, ("Authentication handshake for account %s", mysql->user)); + + // Create connection object. + + Connection con(vio); + DBUG_ASSERT(!con.error()); + + // Read initial packet from server containing service name. + + int ret; + Blob service_name= con.read(); + + if (con.error() || service_name.is_null()) + { + ERROR_LOG(ERROR, ("Error reading initial packet")); + return CR_ERROR; + } + DBUG_PRINT("info", ("Got initial packet of length %d", service_name.len())); + + // Create authentication handsake context using the given service name. + + Handshake_client hndshk(service_name[0] ? (char *)service_name.ptr() : NULL, + service_name.len()); + if (hndshk.error()) + { + ERROR_LOG(ERROR, ("Could not create authentication handshake context")); + return CR_ERROR; + } + + /* + The following packet exchange always starts with a packet sent by + the client. Send this first packet now. + */ + + { + Blob packet= hndshk.first_packet(); + if (hndshk.error() || packet.is_null()) + { + ERROR_LOG(ERROR, ("Could not generate first packet")); + return CR_ERROR; + } + DBUG_PRINT("info", ("Sending first packet of length %d", packet.len())); + + ret= con.write(packet); + if (ret) + { + ERROR_LOG(ERROR, ("Error writing first packet")); + return CR_ERROR; + } + DBUG_PRINT("info", ("First packet sent")); + } + + DBUG_ASSERT(!hndshk.error()); + + /* + If handshake is not yet complete and client expects a reply, + read and process packets from server until handshake is complete. + */ + if (!hndshk.is_complete()) + { + if (hndshk.packet_processing_loop(con)) + return CR_ERROR; + } + + DBUG_ASSERT(!hndshk.error() && hndshk.is_complete()); + + return CR_OK; +} === added file 'libmysql/authentication_win/log_client.cc' --- a/libmysql/authentication_win/log_client.cc 1970-01-01 00:00:00 +0000 +++ b/libmysql/authentication_win/log_client.cc 2011-03-28 16:20:59 +0000 @@ -0,0 +1,55 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include +#include "common.h" + +/** + This option is set in win_auth_handshake_client() function + in handshake_client.cc. + + Values: + 0 - no logging + 1 - log error/warning/info messages + 2 - also log debug messages + + Note: No error or debug messages are logged in production code + (see logging macros in common.h). +*/ +int opt_auth_win_client_log= 0; + + +// Client-side logging function + +void error_log_vprint(error_log_level::type level, + const char *fmt, va_list args) +{ + if (0 == opt_auth_win_client_log) + return; + + const char *level_string= ""; + + switch (level) + { + case error_log_level::INFO: level_string= "Note"; break; + case error_log_level::WARNING: level_string= "Warning"; break; + case error_log_level::ERROR: level_string= "ERROR"; break; + } + + fprintf(stderr, "Windows Authentication Plugin %s: ", level_string); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + fflush(stderr); +} === added file 'libmysql/authentication_win/plugin_client.cc' --- a/libmysql/authentication_win/plugin_client.cc 1970-01-01 00:00:00 +0000 +++ b/libmysql/authentication_win/plugin_client.cc 2011-03-28 16:20:59 +0000 @@ -0,0 +1,58 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include +#include +#include +#include + +#include "common.h" + +static int win_auth_client_plugin_init(char*, size_t, int, va_list) +{ + return 0; +} + + +static int win_auth_client_plugin_deinit() +{ + return 0; +} + + +int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); + + +/* + Client plugin declaration. This is added to mysql_client_builtins[] + in sql-common/client.c +*/ + +extern "C" +st_mysql_client_plugin_AUTHENTICATION win_auth_client_plugin= +{ + MYSQL_CLIENT_AUTHENTICATION_PLUGIN, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, + "authentication_windows_client", + "Rafal Somla", + "Windows Authentication Plugin - client side", + {0,1,0}, + "GPL", + NULL, + win_auth_client_plugin_init, + win_auth_client_plugin_deinit, + NULL, // option handling + win_auth_handshake_client +}; === modified file 'sql-common/client.c' --- a/sql-common/client.c 2011-02-11 14:00:09 +0000 +++ b/sql-common/client.c 2011-03-28 16:20:59 +0000 @@ -2314,11 +2314,18 @@ static auth_plugin_t clear_password_clie clear_password_auth_client }; +#ifdef AUTHENTICATION_WIN +extern auth_plugin_t win_auth_client_plugin; +#endif + struct st_mysql_client_plugin *mysql_client_builtins[]= { (struct st_mysql_client_plugin *)&native_password_client_plugin, (struct st_mysql_client_plugin *)&old_password_client_plugin, (struct st_mysql_client_plugin *)&clear_password_client_plugin, +#ifdef AUTHENTICATION_WIN + (struct st_mysql_client_plugin *)&win_auth_client_plugin, +#endif 0 }; --===============1834032338== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/rafal.somla@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: rafal.somla@stripped # target_branch: file:///D:/rafal/winauth/bug59780/mysql-5.5/ # testament_sha1: af87f08dd2774ec987b74f53f97b226a43559bbf # timestamp: 2011-03-28 18:21:07 +0200 # source_branch: D:/rafal/winauth/bug59780/mysql-5.5 # base_revision_id: build@stripped # # Begin bundle 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 --===============1834032338==--