------------------------------------------------------------ revno: 3078 committer: Sergei Golubchik branch nick: 5.2 timestamp: Fri 2011-12-30 13:57:15 +0100 message: windows auth plugin added: mysql-test/suite/plugins/r/win.result mysql-test/suite/plugins/r/win_debug.result mysql-test/suite/plugins/t/win.test mysql-test/suite/plugins/t/win_debug.test plugin/auth_win/ plugin/auth_win/CMakeLists.txt plugin/auth_win/handshake_server.cc plugin/auth_win/plug.in plugin/auth_win/plugin_server.cc plugin/auth_win_client/ plugin/auth_win_client/CMakeLists.txt plugin/auth_win_client/common.cc plugin/auth_win_client/common.h plugin/auth_win_client/handshake.cc plugin/auth_win_client/handshake.h plugin/auth_win_client/handshake_client.cc plugin/auth_win_client/log_client.cc plugin/auth_win_client/plug.in plugin/auth_win_client/plugin_client.cc modified: mysql-test/suite/plugins/suite.pm diff: === added file 'mysql-test/suite/plugins/r/win.result' --- mysql-test/suite/plugins/r/win.result 1970-01-01 00:00:00 +0000 +++ mysql-test/suite/plugins/r/win.result 2011-12-30 12:57:15 +0000 @@ -0,0 +1,30 @@ +install plugin windows soname 'auth_win.dll'; +# +# with named user +# +create user WIN_USER identified via windows; +# +# name match = ok +# +select user(), current_user(), database(); +user() current_user() database() +WIN_USER@localhost WIN_USER@% test +# +# name does not match = failure +# +drop user WIN_USER; +# +# and now with anonymous user +# +grant SELECT ON test.* TO '' identified via windows; +# +# name match = ok +# +select user(), current_user(), database(); +user() current_user() database() +WIN_USER@localhost @% test +# +# name does not match = failure +# +delete from mysql.user where user=''; +uninstall plugin windows; === added file 'mysql-test/suite/plugins/r/win_debug.result' --- mysql-test/suite/plugins/r/win_debug.result 1970-01-01 00:00:00 +0000 +++ mysql-test/suite/plugins/r/win_debug.result 2011-12-30 12:57:15 +0000 @@ -0,0 +1,10 @@ +install plugin windows soname 'auth_win.dll'; +create user WIN_USER identified via windows; +SET @old_debug=@@global.debug; +SET GLOBAL debug='+d,winauth_first_packet_test'; +select user(), current_user(), database(); +user() current_user() database() +WIN_USER@localhost WIN_USER@% test +SET GLOBAL debug=@old_debug; +drop user WIN_USER; +uninstall plugin windows; === modified file 'mysql-test/suite/plugins/suite.pm' --- mysql-test/suite/plugins/suite.pm 2011-12-02 15:27:13 +0000 +++ mysql-test/suite/plugins/suite.pm 2011-12-30 12:57:15 +0000 @@ -4,5 +4,10 @@ package My::Suite::Plugins; $ENV{PAM_SETUP_FOR_MTR}=1 if -e '/etc/pam.d/mariadb_mtr'; +eval { + require Win32; + $ENV{WIN_USER}=Win32::LoginName; +}; + bless { }; === added file 'mysql-test/suite/plugins/t/win.test' --- mysql-test/suite/plugins/t/win.test 1970-01-01 00:00:00 +0000 +++ mysql-test/suite/plugins/t/win.test 2011-12-30 12:57:15 +0000 @@ -0,0 +1,57 @@ +--source include/not_embedded.inc +--source include/windows.inc + +if (!$AUTH_WIN_SO) { + skip No win auth plugin; +} + +let $plugindir=`SELECT @@global.plugin_dir`; + +eval install plugin windows soname '$AUTH_WIN_SO'; + +--echo # +--echo # with named user +--echo # + +--replace_result $WIN_USER WIN_USER +eval create user $WIN_USER identified via windows; + +--write_file $MYSQLTEST_VARDIR/tmp/win_test.txt +--replace_result $WIN_USER WIN_USER +select user(), current_user(), database(); +EOF + +--echo # +--echo # name match = ok +--echo # +--exec $MYSQL_TEST -u $WIN_USER --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt + +--echo # +--echo # name does not match = failure +--echo # +--error 1 +--exec $MYSQL_TEST -u foobar --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt + +--replace_result $WIN_USER WIN_USER +eval drop user $WIN_USER; + +--echo # +--echo # and now with anonymous user +--echo # +grant SELECT ON test.* TO '' identified via windows; +--echo # +--echo # name match = ok +--echo # +--exec $MYSQL_TEST -u $WIN_USER --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt + +--echo # +--echo # name does not match = failure +--echo # +--error 1 +--exec $MYSQL_TEST -u foobar --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt + +# restoring mysql.user to the original state. +delete from mysql.user where user=''; +uninstall plugin windows; +--remove_file $MYSQLTEST_VARDIR/tmp/win_test.txt + === added file 'mysql-test/suite/plugins/t/win_debug.test' --- mysql-test/suite/plugins/t/win_debug.test 1970-01-01 00:00:00 +0000 +++ mysql-test/suite/plugins/t/win_debug.test 2011-12-30 12:57:15 +0000 @@ -0,0 +1,43 @@ +--source include/not_embedded.inc +--source include/windows.inc +--source include/have_debug.inc + +if (!$AUTH_WIN_SO) { + skip No win auth plugin; +} + +let $plugindir=`SELECT @@global.plugin_dir`; + +eval install plugin windows soname '$AUTH_WIN_SO'; +--replace_result $WIN_USER WIN_USER +eval create user $WIN_USER identified via windows; + +--write_file $MYSQLTEST_VARDIR/tmp/win_test.txt +--replace_result $WIN_USER WIN_USER +select user(), current_user(), database(); +EOF + +SET @old_debug=@@global.debug; +SET GLOBAL debug='+d,winauth_first_packet_test'; + +--exec $MYSQL_TEST -u $WIN_USER --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt +let WINAUTH_FIRST_PACKET_TEST=255; +--error 1 +--exec $MYSQL_TEST -u $WIN_USER --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt +let WINAUTH_FIRST_PACKET_TEST=1234; +--error 1 +--exec $MYSQL_TEST -u $WIN_USER --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt +let WINAUTH_FIRST_PACKET_TEST=600; +--error 1 +--exec $MYSQL_TEST -u $WIN_USER --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt +let WINAUTH_FIRST_PACKET_TEST=601; +--error 1 +--exec $MYSQL_TEST -u $WIN_USER --plugin-dir=$plugindir < $MYSQLTEST_VARDIR/tmp/win_test.txt + +SET GLOBAL debug=@old_debug; + +--replace_result $WIN_USER WIN_USER +eval drop user $WIN_USER; +uninstall plugin windows; +--remove_file $MYSQLTEST_VARDIR/tmp/win_test.txt + === added directory 'plugin/auth_win' === added file 'plugin/auth_win/CMakeLists.txt' --- plugin/auth_win/CMakeLists.txt 1970-01-01 00:00:00 +0000 +++ plugin/auth_win/CMakeLists.txt 2011-12-30 12:57:15 +0000 @@ -0,0 +1,15 @@ +INCLUDE("${PROJECT_SOURCE_DIR}/storage/mysql_storage_engine.cmake") + + +INCLUDE_DIRECTORIES(../auth_win_client) + +SET(AUTH_WIN_SOURCES + ../auth_win_client/common.cc + ../auth_win_client/handshake.cc + ../auth_win_client/log_client.cc + handshake_server.cc plugin_server.cc) + +SET(AUTH_WIN_LIBS Secur32 mysqld) + +MYSQL_PLUGIN(AUTH_WIN) + === added file 'plugin/auth_win/handshake_server.cc' --- plugin/auth_win/handshake_server.cc 1970-01-01 00:00:00 +0000 +++ plugin/auth_win/handshake_server.cc 2011-12-30 12:57:15 +0000 @@ -0,0 +1,158 @@ +/* Copyright (c) 2011, Monty Program Ab + + 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" + +class Handshake_server: public Handshake +{ + Connection &m_con; + +public: + + Handshake_server(Connection &con) + : Handshake(SSP_NAME, SERVER), m_con(con), m_buf(0) {}; + + ~Handshake_server() { delete [] m_buf; }; + + Blob process_data(const Blob&); + + Blob read_packet(); + int write_packet(Blob &data); + + HANDLE sec_token(); + +private: + byte *m_buf; + size_t m_buflen; +}; + +HANDLE Handshake_server::sec_token() +{ + HANDLE handle; + QuerySecurityContextToken(&m_sctx, &handle); + return handle; +} + +Blob Handshake_server::read_packet() +{ + Blob packet= m_con.read(); + m_error= m_con.error(); + if (!m_error && packet.is_null()) + m_error= true; // (no specific error code assigned) + + if (m_error) + return Blob(); + + if (m_round == 1 && packet.len() > 254) + { + m_buflen= packet[254] * 512; + m_buf= new byte[m_buflen]; + if (!m_buflen || !m_buf) + { + m_error= true; + return Blob(); + } + memcpy(m_buf, packet.ptr(), 254); + Blob tail= m_con.read(); + if ((!m_error && packet.is_null()) || tail.len() + 254 > m_buflen) + m_error= true; // (no specific error code assigned) + if (m_error) + return Blob(); + memcpy(m_buf + 254, tail.ptr(), tail.len()); + packet= Blob(m_buf, tail.len() + 254); + DBUG_EXECUTE_IF("winauth_first_packet_test", { + size_t len= m_buf[0] + (m_buf[1] << 8); + byte *ptr= packet.ptr() + 2; + DBUG_ASSERT(len == packet.len()); + for (byte b= 0; ptr < packet.ptr() + len; ++ptr, ++b) + DBUG_ASSERT(*ptr == b); + }); + } + + DBUG_PRINT("dump", ("Got the following bytes")); + DBUG_DUMP("dump", packet.ptr(), packet.len()); + return packet; +} + +int Handshake_server::write_packet(Blob &data) +{ + DBUG_PRINT("dump", ("Sending the following data")); + DBUG_DUMP("dump", data.ptr(), data.len()); + return m_error= m_con.write(data); +} + +Blob Handshake_server::process_data(const Blob &data) +{ + Security_buffer input(data); + SECURITY_STATUS ret; + + m_output.free(); + + ret= AcceptSecurityContext( + &m_cred, + m_round == 1 ? NULL : &m_sctx, // partial context + &input, // input data + ASC_REQ_ALLOCATE_MEMORY, // requested attributes + SECURITY_NETWORK_DREP, // data representation + &m_sctx, // context + &m_output, // output data + &m_atts, // attributes + &m_expire); // expire date + + if (process_result(ret)) + { + DBUG_PRINT("error", + ("AcceptSecurityContext() failed with error %X", ret)); + return Blob(); + } + + return m_output.as_blob(); +} + +/**********************************************************************/ + +int win_auth_handshake_server(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) +{ + Connection con(vio); + DBUG_ENTER("win_auth_handshake_server"); + + ERROR_LOG(INFO, ("Authentication handshake for account %s", info->user_name)); + + UPN service_name; + con.write(service_name.as_blob()); + DBUG_PRINT("info", ("Service name: %s", service_name.as_string())); + + Handshake_server hndshk(con); + if (hndshk.error()) + { + ERROR_LOG(ERROR, ("Could not create authentication handshake context")); + DBUG_RETURN(CR_ERROR); + } + + if (hndshk.packet_processing_loop()) + DBUG_RETURN(CR_ERROR); + + DBUG_ASSERT(!hndshk.error() && hndshk.is_complete()); + + Sid client_sid(hndshk.sec_token()); + + char buf[MYSQL_USERNAME_LENGTH+1]; + if (!client_sid.is_valid() || + !client_sid.make_username(buf, sizeof(buf)) || + stricmp(info->user_name, buf)) + DBUG_RETURN(CR_ERROR); + + DBUG_RETURN(CR_OK); +} === added file 'plugin/auth_win/plug.in' --- plugin/auth_win/plug.in 1970-01-01 00:00:00 +0000 +++ plugin/auth_win/plug.in 2011-12-30 12:57:15 +0000 @@ -0,0 +1,3 @@ +MYSQL_PLUGIN(auth_win, [Windows Authentication Plugin], [Windows Authentication Plugin - Server Side]) +MYSQL_PLUGIN_DYNAMIC(auth_win, [auth_win.la]) +MYSQL_PLUGIN_DISABLED(auth_win) === added file 'plugin/auth_win/plugin_server.cc' --- plugin/auth_win/plugin_server.cc 1970-01-01 00:00:00 +0000 +++ plugin/auth_win/plugin_server.cc 2011-12-30 12:57:15 +0000 @@ -0,0 +1,43 @@ +/* Copyright (c) 2011, Monty Program Ab + + 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" + +int win_auth_handshake_server(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info); + +static struct st_mysql_auth info= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "authentication_windows_client", + win_auth_handshake_server +}; + +maria_declare_plugin(auth_win) +{ + MYSQL_AUTHENTICATION_PLUGIN, + &info, + "windows", + "Sergei Golubchik", + "Windows Authentication Plugin", + PLUGIN_LICENSE_GPL, + NULL, + NULL, + 0x0100, + NULL, + NULL, + "1.0", + MariaDB_PLUGIN_MATURITY_EXPERIMENTAL, +} +maria_declare_plugin_end; === added directory 'plugin/auth_win_client' === added file 'plugin/auth_win_client/CMakeLists.txt' --- plugin/auth_win_client/CMakeLists.txt 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/CMakeLists.txt 2011-12-30 12:57:15 +0000 @@ -0,0 +1,9 @@ +INCLUDE("${PROJECT_SOURCE_DIR}/storage/mysql_storage_engine.cmake") + +SET(AUTHENTICATION_WINDOWS_CLIENT_SOURCES plugin_client.cc handshake_client.cc + log_client.cc common.cc handshake.cc) + +SET(AUTHENTICATION_WINDOWS_CLIENT_LIBS Secur32) + +MYSQL_PLUGIN(AUTHENTICATION_WINDOWS_CLIENT) + === added file 'plugin/auth_win_client/common.cc' --- plugin/auth_win_client/common.cc 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/common.cc 2011-12-30 12:57:15 +0000 @@ -0,0 +1,476 @@ +/* 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, ...); + +/** + Option indicating desired level of logging. Values: + + 0 - no logging + 1 - log only error messages + 2 - additionally log warnings + 3 - additionally log info notes + 4 - also log debug messages + + Value of this option should be taken into account in the + implementation of error_log_vprint() function (see + log_client.cc). + + Note: No error or debug messages are logged in production code + (see logging macros in common.h). +*/ +int opt_auth_win_log_level= 2; + + +/** 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) +{ + 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) + { + 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))); + 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()) + { + 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))); + 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) +{ + DWORD req_size= 0; + bool success; + + // Determine required buffer size + + success= GetTokenInformation(token, TokenUser, NULL, 0, &req_size); + if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + 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))); + 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; + 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))); + } + } +} + + +Sid::~Sid() +{ + if (m_data) + delete [] m_data; +} + +/// Check if Sid object is valid. +bool Sid::is_valid(void) const +{ + return m_data && m_data->User.Sid && IsValidSid(m_data->User.Sid); +} + +bool Sid::make_username(char *buf, DWORD buflen) +{ + if (!m_data) + return false; + + SID_NAME_USE use; + char domain[MAX_SERVICE_NAME_LENGTH]; + DWORD domain_len= sizeof(domain); + bool success= LookupAccountSid(NULL, m_data->User.Sid, + buf, &buflen, domain, &domain_len, &use); + + if (!success) + { + Error_message_buf error_buf; + DBUG_PRINT("error", ("LookupAccountSid() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); + return false; + } + + return true; +} + + + +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 'plugin/auth_win_client/common.h' --- plugin/auth_win_client/common.h 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/common.h 2011-12-30 12:57:15 +0000 @@ -0,0 +1,316 @@ +/* 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 +#define DONT_DEFINE_VOID 1 +#include +#define SECURITY_WIN32 1 +#include // for MYSQL_PLUGIN_VIO +#include +#include // for CtxtHandle + +/// 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; +}; + +extern "C" int opt_auth_win_log_level; +unsigned int get_log_level(void); +void set_log_level(unsigned int); + + +/* + 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: Macro ERROR_LOG() can use printf-like format string like this: + + ERROR_LOG(Level, ("format string", args)); + + The implementation should handle it correctly. Currently it is passed + to fprintf() (see error_log_vprint() function). +*/ + +#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 + + +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); + + +/* + Internal implementation of debug message printing which does not use + dbug library. This is invoked via macro: + + DBUG_PRINT_DO(Keyword, ("format string", args)); + + This is supposed to be used as an implementation of DBUG_PRINT() macro, + unless the dbug library implementation is used or debug messages are disabled. +*/ + +#ifndef DBUG_OFF + +#define DBUG_PRINT_DO(Keyword, Msg) \ + do { \ + if (4 > get_log_level()) break; \ + fprintf(stderr, "winauth: %s: ", Keyword); \ + debug_msg Msg; \ + } while (0) + +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); +} + +#else +#define DBUG_PRINT_DO(K, M) do {} while (0) +#endif + + +#ifndef WINAUTH_USE_DBUG_LIB + +#undef DBUG_PRINT +#define DBUG_PRINT(Keyword, Msg) DBUG_PRINT_DO(Keyword, Msg) + +/* + Redefine few more debug macros to make sure that no symbols from + dbug library are used. +*/ + +#undef DBUG_ENTER +#define DBUG_ENTER(X) do {} while (0) + +#undef DBUG_RETURN +#define DBUG_RETURN(X) return (X) + +#undef DBUG_ASSERT +#ifndef DBUG_OFF +#define DBUG_ASSERT(X) assert (X) +#else +#define DBUG_ASSERT(X) do {} while (0) +#endif + +#undef DBUG_DUMP +#define DBUG_DUMP(A,B,C) do {} while (0) + +#endif + + +/** 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 + { + static byte out_of_range= 0; // alas, no exceptions... + return pos < len() ? m_ptr[pos] : out_of_range; + } + + bool is_null() const + { + return m_ptr == NULL; + } + + void trim(size_t l) + { + m_len= l; + } +}; + + +/** 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; + } + bool make_username(char *buf, DWORD buflen); +}; + + +/** 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 'plugin/auth_win_client/handshake.cc' --- plugin/auth_win_client/handshake.cc 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/handshake.cc 2011-12-30 12:57:15 +0000 @@ -0,0 +1,289 @@ +/* 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() +{ + m_round= 0; + + do { + ++m_round; + // Read packet send by the peer + + DBUG_PRINT("info", ("Waiting for packet")); + Blob packet= read_packet(); + if (error()) + { + ERROR_LOG(ERROR, ("Error reading packet in round %d", m_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", m_round)); + return 1; + } + + /* + If new data has been generated, send it to the peer. Otherwise + handshake must be completed. + */ + + if (!new_data.is_null()) + { + DBUG_PRINT("info", ("Round %d started", m_round)); + + DBUG_PRINT("info", ("Sending packet of length %d", new_data.len())); + int ret= write_packet(new_data); + if (ret) + { + ERROR_LOG(ERROR, ("Error writing packet in round %d", m_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", m_round)); + return 1; + } + + /* + To protect against malicious clients, break handshake exchange if + too many rounds. + */ + + if (m_round > MAX_HANDSHAKE_ROUNDS) + { + ERROR_LOG(ERROR, ("Authentication handshake could not be completed" + " after %d rounds", m_round)); + return 1; + } + + } while(!is_complete()); + + ERROR_LOG(INFO, ("Handshake completed after %d rounds", m_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 'plugin/auth_win_client/handshake.h' --- plugin/auth_win_client/handshake.h 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/handshake.h 2011-12-30 12:57:15 +0000 @@ -0,0 +1,181 @@ +/* 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(); + + 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; + + /** + Round of the handshake (starting from round 1). One round + consist of reading packet from the other side, processing it and + optionally sending a reply (see @c packet_processing_loop()). + */ + unsigned int m_round; + + /// 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; + + /// Read packet from the other end. + virtual Blob read_packet() =0; + + /// Write packet to the other end. + virtual int write_packet(Blob &data) =0; + +#ifndef DBUG_OFF + +private: + SecPkgInfo *m_ssp_info; +public: + const char* ssp_name(); + +#endif +}; + + +#endif === added file 'plugin/auth_win_client/handshake_client.cc' --- plugin/auth_win_client/handshake_client.cc 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/handshake_client.cc 2011-12-30 12:57:15 +0000 @@ -0,0 +1,386 @@ +/* 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]; + + Connection &m_con; + +public: + + Handshake_client(Connection &con, const char *target, size_t len); + ~Handshake_client(); + + Blob first_packet(); + Blob process_data(const Blob&); + + Blob read_packet(); + int write_packet(Blob &data); +}; + + +/** + Create authentication handshake context for client. + + @param con connection for communication with the peer + @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(Connection &con, + const char *target, size_t len) +: Handshake(SSP_NAME, CLIENT), m_service_name(NULL), m_con(con) +{ + 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); +} + + +Blob Handshake_client::read_packet() +{ + /* + We do a fake read in the first round because first + packet from the server containing UPN must be read + before the handshake context is created and the packet + processing loop starts. We return an empty blob here + and process_data() function will ignore it. + */ + if (m_round == 1) + return Blob(); + + // Otherwise we read packet from the connection. + + Blob packet= m_con.read(); + m_error= m_con.error(); + if (!m_error && packet.is_null()) + m_error= true; // (no specific error code assigned) + + if (m_error) + return Blob(); + + DBUG_PRINT("dump", ("Got the following bytes")); + DBUG_DUMP("dump", packet.ptr(), packet.len()); + return packet; +} + + + +int Handshake_client::write_packet(Blob &data) +{ + /* + Length of the first data payload send by client authentication plugin is + limited to 255 bytes (because it is wrapped inside client authentication + packet and is length-encoded with 1 byte for the length). + + If the data payload is longer than 254 bytes, then it is sent in two parts: + first part of length 255 will be embedded in the authentication packet, + second part will be sent in the following packet. Byte 255 of the first + part contains information about the total length of the payload. It is a + number of blocks of size 512 bytes which is sufficient to store the + combined packets. + + Server's logic for reading first client's payload is as follows + (see Handshake_server::read_packet()): + 1. Read data from the authentication packet, if it is shorter than 255 bytes + then that is all data sent by client. + 2. If there is 255 bytes of data in the authentication packet, read another + packet and append it to the data, skipping byte 255 of the first packet + which can be used to allocate buffer of appropriate size. + */ + + size_t len2= 0; // length of the second part of first data payload + byte saved_byte; // for saving byte 255 in which data length is stored + + if (m_round == 1 && data.len() > 254) + { + len2= data.len() - 254; + DBUG_PRINT("info", ("Splitting first packet of length %lu" + ", %lu bytes will be sent in a second part", + data.len(), len2)); + /* + Store in byte 255 the number of 512b blocks that are needed to + keep all the data. + */ + unsigned block_count= data.len()/512 + ((data.len() % 512) ? 1 : 0); + +#if !defined(DBUG_OFF) + + /* + For testing purposes, use wrong block count to see how server + handles this. + */ + if (getenv("WINAUTH_FIRST_PACKET_TEST")) + { + block_count= data.len() == 601 ? 0 : + data.len() == 602 ? 1 : + block_count; + } +#endif + + DBUG_ASSERT(block_count < (unsigned)0x100); + saved_byte= data[254]; + data[254] = block_count; + + data.trim(255); + } + + DBUG_PRINT("dump", ("Sending the following data")); + DBUG_DUMP("dump", data.ptr(), data.len()); + int ret= m_con.write(data); + + if (ret) + return ret; + + // Write second part if it is present. + if (len2) + { + data[254]= saved_byte; + Blob data2(data.ptr() + 254, len2); + DBUG_PRINT("info", ("Sending second part of data")); + DBUG_DUMP("info", data2.ptr(), data2.len()); + ret= m_con.write(data2); + } + + return ret; +} + + +/** + 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. + + When invoked for the first time (in the first round of the handshake) + there is no data from the server (data blob is null) and the intial + packet is generated without an input. + + @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) +{ +#if !defined(DBUG_OFF) + /* + Code for testing the logic for sending the first client payload. + + A fake data of length given by environment variable WINAUTH_FIRST_PACKET_TEST + is sent to the server. First 2 bytes of the + payload contain its total length (LSB first). The length of test data + is limited to 2048 bytes. + + Upon receiving test data, server will check that data is correct and + refuse connection. If server detects data errors it will crash on + assertion. + + This code is executed only if WINAUTH_FIRST_PACKET_TEST is set. + + A corresponding debug flag must be enabled in the server, e.g. using + statement: + + SET GLOBAL debug= '+d,winauth_first_packet_test'; + */ + + static byte test_buf[2048]; + + if (m_round == 1 && getenv("WINAUTH_FIRST_PACKET_TEST")) + { + size_t len= atoi(getenv("WINAUTH_FIRST_PACKET_TEST")); + if (len > sizeof(test_buf)) + len= sizeof(test_buf); + + // Store data length in first 2 bytes. + byte *ptr= test_buf; + *ptr++= len & 0xFF; + *ptr++= len >> 8; + + // Fill remaining bytes with known values. + for (byte b= 0; ptr < test_buf + len; ++ptr, ++b) + *ptr= b; + + return Blob(test_buf, len); + }; + +#endif + + Security_buffer input(data); + SECURITY_STATUS ret; + + m_output.free(); + + ret= InitializeSecurityContextW( + &m_cred, + m_round == 1 ? NULL : &m_sctx, // partial context + m_service_name, // service name + ASC_REQ_ALLOCATE_MEMORY, // requested attributes + 0, // reserved + SECURITY_NETWORK_DREP, // data representation + m_round == 1 ? NULL : &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) +{ + DBUG_ENTER("win_auth_handshake_client"); + + /* + 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= 2; + if (!strncasecmp("yes", opt, 3)) opt_val= 2; + if (!strncasecmp("true", opt, 4)) opt_val= 2; + if (!strncasecmp("debug", opt, 5)) opt_val= 4; + if (!strncasecmp("dbug", opt, 4)) opt_val= 4; + } + set_log_level(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. + + Blob service_name= con.read(); + + if (con.error() || service_name.is_null()) + { + ERROR_LOG(ERROR, ("Error reading initial packet")); + DBUG_RETURN(CR_ERROR); + } + DBUG_PRINT("info", ("Got initial packet of length %d", service_name.len())); + + // Create authentication handshake context using the given service name. + + Handshake_client hndshk(con, + service_name[0] ? (char *)service_name.ptr() : NULL, + service_name.len()); + if (hndshk.error()) + { + ERROR_LOG(ERROR, ("Could not create authentication handshake context")); + DBUG_RETURN(CR_ERROR); + } + + DBUG_ASSERT(!hndshk.error()); + + /* + Read and process packets from server until handshake is complete. + Note that the first read from server is dummy + (see Handshake_client::read_packet()) as we already have read the + first packet to establish service name. + */ + if (hndshk.packet_processing_loop()) + DBUG_RETURN(CR_ERROR); + + DBUG_ASSERT(!hndshk.error() && hndshk.is_complete()); + + DBUG_RETURN(CR_OK); +} === added file 'plugin/auth_win_client/log_client.cc' --- plugin/auth_win_client/log_client.cc 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/log_client.cc 2011-12-30 12:57:15 +0000 @@ -0,0 +1,64 @@ +/* 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" + + +// Client-side logging function + +void error_log_vprint(error_log_level::type level, + const char *fmt, va_list args) +{ + const char *level_string= ""; + int log_level= get_log_level(); + + switch (level) + { + case error_log_level::INFO: + if (3 > log_level) + return; + level_string= "Note"; + break; + case error_log_level::WARNING: + if (2 > log_level) + return; + level_string= "Warning"; + break; + case error_log_level::ERROR: + if (1 > log_level) + return; + level_string= "ERROR"; + break; + } + + fprintf(stderr, "Windows Authentication Plugin %s: ", level_string); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + fflush(stderr); +} + + +// Trivial implementation of log-level setting storage. + +void set_log_level(unsigned int level) +{ + opt_auth_win_log_level= level; +} + + +unsigned int get_log_level(void) +{ + return opt_auth_win_log_level; +} === added file 'plugin/auth_win_client/plug.in' --- plugin/auth_win_client/plug.in 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/plug.in 2011-12-30 12:57:15 +0000 @@ -0,0 +1,3 @@ +MYSQL_PLUGIN(authentication_windows_client, [Windows Authentication Plugin], [Windows Authentication Plugin - Client Side]) +MYSQL_PLUGIN_DYNAMIC(authentication_windows_client, [authentication_windows_client.la]) +MYSQL_PLUGIN_DISABLED(authentication_windows_client) === added file 'plugin/auth_win_client/plugin_client.cc' --- plugin/auth_win_client/plugin_client.cc 1970-01-01 00:00:00 +0000 +++ plugin/auth_win_client/plugin_client.cc 2011-12-30 12:57:15 +0000 @@ -0,0 +1,29 @@ +/* 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 + +int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql); + +mysql_declare_client_plugin(AUTHENTICATION) + "authentication_windows_client", + "Rafal Somla", + "Windows Authentication Plugin - client side", + {0,1,0}, + NULL, + NULL, + win_auth_handshake_client +mysql_end_client_plugin;