Merge branch '2.2' into develop

This commit is contained in:
Johan Wikman
2018-01-29 12:59:06 +02:00
6 changed files with 83 additions and 58 deletions

View File

@ -48,7 +48,7 @@ cp _build/*.gz .
set -x set -x
if [ "$build_experimental" == "yes" ] if [ "$build_experimental" == "yes" ]
then then
for component in experimental devel client for component in experimental devel
do do
cd _build cd _build
rm CMakeCache.txt rm CMakeCache.txt

View File

@ -43,7 +43,7 @@ cp _build/*.gz .
if [ "$build_experimental" == "yes" ] if [ "$build_experimental" == "yes" ]
then then
for component in experimental devel client for component in experimental devel
do do
cd _build cd _build
rm CMakeCache.txt rm CMakeCache.txt

View File

@ -27,6 +27,9 @@ set(BUILD_CDC TRUE CACHE BOOL "Build Avro router")
# Build the multimaster monitor # Build the multimaster monitor
set(BUILD_MMMON TRUE CACHE BOOL "Build multimaster monitor") set(BUILD_MMMON TRUE CACHE BOOL "Build multimaster monitor")
# Build MaxCtrl
set(BUILD_MAXCTRL TRUE CACHE BOOL "Build MaxCtrl")
# Build Luafilter # Build Luafilter
set(BUILD_LUAFILTER FALSE CACHE BOOL "Build Luafilter") set(BUILD_LUAFILTER FALSE CACHE BOOL "Build Luafilter")

View File

@ -43,7 +43,7 @@
MXS_BEGIN_DECLS MXS_BEGIN_DECLS
#define GW_MYSQL_VERSION "5.5.5-10.0.0 " MAXSCALE_VERSION "-maxscale" #define GW_MYSQL_VERSION "5.5.5-10.2.12 " MAXSCALE_VERSION "-maxscale"
#define GW_MYSQL_LOOP_TIMEOUT 300000000 #define GW_MYSQL_LOOP_TIMEOUT 300000000
#define GW_MYSQL_READ 0 #define GW_MYSQL_READ 0
#define GW_MYSQL_WRITE 1 #define GW_MYSQL_WRITE 1

View File

@ -1,3 +1,4 @@
if (BUILD_MAXCTRL)
find_package(NPM) find_package(NPM)
find_package(NodeJS) find_package(NodeJS)
@ -6,7 +7,7 @@ if (NPM_FOUND AND NODEJS_FOUND AND NODEJS_VERSION VERSION_GREATER "6.0.0")
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.sh ${CMAKE_SOURCE_DIR} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.sh ${CMAKE_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_custom_target(maxctrl ALL DEPENDS ${CMAKE_BINARY_DIR}/maxctrl/maxctrl) add_custom_target(maxctrl ALL DEPENDS ${CMAKE_BINARY_DIR}/maxctrl/maxctrl)
install_script(${CMAKE_BINARY_DIR}/maxctrl/maxctrl client) install_script(${CMAKE_BINARY_DIR}/maxctrl/maxctrl core)
add_custom_target(test_maxctrl add_custom_target(test_maxctrl
COMMAND ${CMAKE_SOURCE_DIR}/test/run_npm_test.sh COMMAND ${CMAKE_SOURCE_DIR}/test/run_npm_test.sh
@ -16,5 +17,8 @@ if (NPM_FOUND AND NODEJS_FOUND AND NODEJS_VERSION VERSION_GREATER "6.0.0")
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
else() else()
message(STATUS "Not building MaxCtrl: npm or Node.js >= 6.0.0 not found") message(FATAL_ERROR "Not building MaxCtrl: npm or Node.js >= 6.0.0 not found. Add the following to skip MaxCtrl: -DBUILD_MAXCTRL=N")
endif()
else()
messages(STATUS "Not building MaxCtrl: BUILD_MAXCTRL=N")
endif() endif()

View File

@ -138,15 +138,15 @@ int conversation_func(int num_msg, const struct pam_message **msg,
} }
/** /**
* @brief Check if the client token is valid * @brief Check if the client password is correct for the service
* *
* @param token Client token * @param user Username
* @param len Length of the token * @param password Password
* @param output Pointer where the client principal name is stored * @param service Which PAM service is the user logging to
* @return True if client token is valid * @param client Client DCB
* @return True if username & password are ok
*/ */
bool validate_pam_password(const string& user, const string& password, bool validate_pam_password(const string& user, const string& password, const string& service, DCB* client)
const string& service, DCB* client)
{ {
ConversationData appdata(client, 0, password); ConversationData appdata(client, 0, password);
pam_conv conv_struct = {conversation_func, &appdata}; pam_conv conv_struct = {conversation_func, &appdata};
@ -164,8 +164,7 @@ bool validate_pam_password(const string& user, const string& password,
MXS_DEBUG("pam_authenticate returned success."); MXS_DEBUG("pam_authenticate returned success.");
break; break;
case PAM_AUTH_ERR: case PAM_AUTH_ERR:
MXS_DEBUG("pam_authenticate returned authentication failure" MXS_DEBUG("pam_authenticate returned authentication failure (wrong password).");
" (wrong password).");
// Normal failure // Normal failure
break; break;
default: default:
@ -223,13 +222,11 @@ PamClientSession* PamClientSession::create(const PamInstance& inst)
} }
/** /**
* @brief Check which PAM services the session user has access to * Check which PAM services the session user has access to.
* *
* @param auth Authenticator session
* @param dcb Client DCB * @param dcb Client DCB
* @param session MySQL session * @param session MySQL session
* * @param services_out Output for services
* @return An array of PAM service names for the session user
*/ */
void PamClientSession::get_pam_user_services(const DCB* dcb, const MYSQL_session* session, void PamClientSession::get_pam_user_services(const DCB* dcb, const MYSQL_session* session,
StringVector* services_out) StringVector* services_out)
@ -241,28 +238,14 @@ void PamClientSession::get_pam_user_services(const DCB* dcb, const MYSQL_session
"' LIKE db) ORDER BY authentication_string"; "' LIKE db) ORDER BY authentication_string";
MXS_DEBUG("PAM services search sql: '%s'.", services_query.c_str()); MXS_DEBUG("PAM services search sql: '%s'.", services_query.c_str());
char *err; char *err;
/**
* Try search twice: first time with the current users, second
* time with fresh users.
*/
for (int i = 0; i < 2; i++)
{
if (i == 0 || service_refresh_users(dcb->service) == 0)
{
if (sqlite3_exec(m_dbhandle, services_query.c_str(), user_services_cb, if (sqlite3_exec(m_dbhandle, services_query.c_str(), user_services_cb,
services_out, &err) != SQLITE_OK) services_out, &err) != SQLITE_OK)
{ {
MXS_ERROR("Failed to execute query: '%s'", err); MXS_ERROR("Failed to execute query: '%s'", err);
sqlite3_free(err); sqlite3_free(err);
} }
else if (!services_out->empty())
{
MXS_DEBUG("User '%s' matched %lu rows in %s db.", session->user, MXS_DEBUG("User '%s' matched %lu rows in %s db.", session->user,
services_out->size(), m_instance.m_tablename.c_str()); services_out->size(), m_instance.m_tablename.c_str());
break;
}
}
}
} }
/** /**
@ -328,16 +311,51 @@ int PamClientSession::authenticate(DCB* dcb)
* responded with the password. Try to continue authentication without more * responded with the password. Try to continue authentication without more
* messages to client. */ * messages to client. */
string password((char*)ses->auth_token, ses->auth_token_len); string password((char*)ses->auth_token, ses->auth_token_len);
/*
* Authentication may be attempted twice: first with old user account info and then with
* updated info. Updating may fail if it has been attempted too often lately. The second password
* check is useless if the user services are same as on the first attempt.
*/
bool authenticated = false;
StringVector services_old;
for (int loop = 0; loop < 2 && !authenticated; loop++)
{
if (loop == 0 || service_refresh_users(dcb->service) == 0)
{
bool try_validate = true;
StringVector services; StringVector services;
get_pam_user_services(dcb, ses, &services); get_pam_user_services(dcb, ses, &services);
if (loop == 0)
for (StringVector::const_iterator i = services.begin(); i != services.end(); i++)
{ {
if (validate_pam_password(ses->user, password, *i, dcb)) services_old = services;
}
else if (services == services_old)
{
try_validate = false;
}
if (try_validate)
{
for (StringVector::iterator iter = services.begin();
iter != services.end() && !authenticated;
iter++)
{
// The server PAM plugin uses "mysql" as the default service when authenticating
// a user with no service.
if (iter->empty())
{
*iter = "mysql";
}
if (validate_pam_password(ses->user, password, *iter, dcb))
{
authenticated = true;
}
}
}
}
}
if (authenticated)
{ {
rval = MXS_AUTH_SUCCEEDED; rval = MXS_AUTH_SUCCEEDED;
break;
}
} }
} }
} }