From: Rafal Somla Date: March 18 2011 2:43pm Subject: bzr commit into mysql-5.5 branch (rafal.somla:3374) Bug#59780 List-Archive: http://lists.mysql.com/commits/133322 X-Bug: 59780 Message-Id: <201103181441.p2IEfoCD002988@acsmt357.oracle.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============1935923539==" --===============1935923539== 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-18 BUG#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. 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-18 14:43:08 +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-18 14:43:08 +0000 @@ -0,0 +1,30 @@ +# 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) + +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-18 14:43:08 +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-18 14:43:08 +0000 @@ -0,0 +1,277 @@ +/* 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 + +/* + 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 { \ + 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) + +#define ERROR_LOG(Level, Msg) error_log_print< error_log_level::Level > Msg + + +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-18 14:43:08 +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-18 14:43:08 +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-18 14:43:08 +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(); +} + + +/**********************************************************************/ + + +bool opt_auth_win_client_log= false; + +/** + 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"); + bool opt_val= opt ? atoi(opt) : 0; + if (opt && !opt_val) + { + opt_val= opt_val || !strncasecmp("on", opt, 2); + opt_val= opt_val || !strncasecmp("yes", opt, 3); + opt_val= opt_val || !strncasecmp("true", opt, 4); + } + 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-18 14:43:08 +0000 @@ -0,0 +1,49 @@ +/* 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" + +extern bool opt_auth_win_client_log; // defined in handshake_client.cc + + +// Client-side logging function + +void error_log_vprint(error_log_level::type level, + const char *fmt, va_list args) +{ + /* + Unless this is a debug build, do not log anything if auth_win_client_log + option is not enabled. + */ +#ifdef DBUG_OFF + if (!opt_auth_win_client_log) + return; +#endif + + 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-18 14:43:08 +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-18 14:43:08 +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 }; --===============1935923539== 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: f6cb9b88fb9ca9b317abe4d194e9a95bdc24e731 # timestamp: 2011-03-18 15:43:24 +0100 # source_branch: D:/rafal/winauth/bug59780/mysql-5.5 # base_revision_id: build@stripped # # Begin bundle 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 kJRG5a8= --===============1935923539==--