
Made the packet sequence number handling automatic so that it always uses the correct one. All functions now have documentation in them. Cleaned up code and added comments to GSSAPI code.
285 lines
7.6 KiB
C
285 lines
7.6 KiB
C
/*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
|
|
*
|
|
* Change Date: 2019-07-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
|
|
#include <maxscale/gw_authenticator.h>
|
|
#include <maxscale/alloc.h>
|
|
#include <maxscale/dcb.h>
|
|
#include <maxscale/log_manager.h>
|
|
#include <maxscale/protocol/mysql.h>
|
|
#include "gssapi_auth.h"
|
|
|
|
/**
|
|
* @file gssapi_backend_auth.c - GSSAPI backend authenticator
|
|
*/
|
|
|
|
/**
|
|
* @brief Create a new GSSAPI token
|
|
* @param dcb Backend DCB
|
|
* @return True on success, false on error
|
|
*/
|
|
static bool send_new_auth_token(DCB *dcb)
|
|
{
|
|
bool rval = false;
|
|
OM_uint32 major = 0, minor = 0;
|
|
gss_ctx_id_t handle = NULL;
|
|
gss_buffer_desc in = {0, 0};
|
|
gss_buffer_desc out = {0, 0};
|
|
gss_buffer_desc target = {0, 0};
|
|
gss_name_t princ = GSS_C_NO_NAME;
|
|
gssapi_auth_t *auth = (gssapi_auth_t*)dcb->authenticator_data;
|
|
|
|
/** The service principal name is sent by the backend server */
|
|
target.value = auth->principal_name;
|
|
target.length = auth->principal_name_len + 1;
|
|
|
|
/** Convert the name into GSSAPI format */
|
|
major = gss_import_name(&minor, &target, GSS_C_NT_USER_NAME, &princ);
|
|
|
|
if (GSS_ERROR(major))
|
|
{
|
|
report_error(major, minor);
|
|
}
|
|
|
|
/** Request the token for the service */
|
|
major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL,
|
|
&handle, princ, GSS_C_NO_OID, 0, 0,
|
|
GSS_C_NO_CHANNEL_BINDINGS, &in, NULL, &out, 0, 0);
|
|
if (GSS_ERROR(major))
|
|
{
|
|
report_error(major, minor);
|
|
}
|
|
else
|
|
{
|
|
/** We successfully requested the token, send it to the backend server */
|
|
GWBUF *buffer = gwbuf_alloc(MYSQL_HEADER_LEN + out.length);
|
|
|
|
if (buffer)
|
|
{
|
|
uint8_t *data = (uint8_t*)GWBUF_DATA(buffer);
|
|
gw_mysql_set_byte3(data, out.length);
|
|
data += 3;
|
|
*data++ = ++auth->sequence;
|
|
memcpy(data, out.value, out.length);
|
|
|
|
if (dcb_write(dcb, buffer))
|
|
{
|
|
rval = true;
|
|
}
|
|
}
|
|
|
|
major = gss_delete_sec_context(&minor, &handle, &in);
|
|
|
|
if (GSS_ERROR(major))
|
|
{
|
|
report_error(major, minor);
|
|
}
|
|
|
|
major = gss_release_name(&minor, &princ);
|
|
|
|
if (GSS_ERROR(major))
|
|
{
|
|
report_error(major, minor);
|
|
}
|
|
}
|
|
|
|
return rval;
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief Extract the principal name from the AuthSwitchRequest packet
|
|
*
|
|
* @param dcb Backend DCB
|
|
* @param buffer Buffer containing an AuthSwitchRequest packet
|
|
* @return True on success, false on error
|
|
*/
|
|
bool extract_principal_name(DCB *dcb, GWBUF *buffer)
|
|
{
|
|
bool rval = false;
|
|
size_t buflen = gwbuf_length(buffer) - MYSQL_HEADER_LEN;
|
|
uint8_t databuf[buflen];
|
|
uint8_t *data = databuf;
|
|
gssapi_auth_t *auth = (gssapi_auth_t*)dcb->authenticator_data;
|
|
|
|
/** Copy the payload and the current packet sequence number */
|
|
gwbuf_copy_data(buffer, MYSQL_HEADER_LEN, buflen, databuf);
|
|
gwbuf_copy_data(buffer, MYSQL_SEQ_OFFSET, 1, &auth->sequence);
|
|
|
|
if (databuf[0] != MYSQL_REPLY_AUTHSWITCHREQUEST)
|
|
{
|
|
/** Server responded with something we did not expect. If it's an OK packet,
|
|
* it's possible that the server authenticated us as the anonymous user. This
|
|
* means that the server is not secure. */
|
|
MXS_ERROR("Server '%s' returned an unexpected authentication response.%s",
|
|
dcb->server->unique_name, databuf[0] == MYSQL_REPLY_OK ?
|
|
" Authentication was complete before it even started, "
|
|
"anonymous users might not be disabled." : "");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* The AuthSwitchRequest packet
|
|
*
|
|
* 0xfe - Command byte
|
|
* string[NUL] - Auth plugin name
|
|
* string[EOF] - Auth plugin data
|
|
*
|
|
* Skip over the auth plugin name and copy the service principal name stored
|
|
* in the auth plugin data section.
|
|
*/
|
|
while (*data && data < databuf + buflen)
|
|
{
|
|
data++;
|
|
}
|
|
|
|
data++;
|
|
buflen -= data - databuf;
|
|
|
|
if (buflen > 0)
|
|
{
|
|
uint8_t *principal = MXS_MALLOC(buflen + 1);
|
|
|
|
if (principal)
|
|
{
|
|
/** Store the principal name for later when we request the token
|
|
* from the GSSAPI server */
|
|
memcpy(principal, data, buflen);
|
|
principal[buflen] = '\0';
|
|
auth->principal_name = principal;
|
|
auth->principal_name_len = buflen;
|
|
rval = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Backend server did not send any auth plugin data.");
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* @brief Extract data from a MySQL packet
|
|
* @param dcb Backend DCB
|
|
* @param buffer Buffer containing a complete packet
|
|
* @return MXS_AUTH_INCOMPLETE if authentication is ongoing, MXS_AUTH_SUCCEEDED
|
|
* if authentication is complete and MXS_AUTH_FAILED if authentication failed.
|
|
*/
|
|
static int gssapi_backend_auth_extract(DCB *dcb, GWBUF *buffer)
|
|
{
|
|
int rval = MXS_AUTH_FAILED;
|
|
gssapi_auth_t *auth = (gssapi_auth_t*)dcb->authenticator_data;
|
|
|
|
if (auth->state == GSSAPI_AUTH_INIT && extract_principal_name(dcb, buffer))
|
|
{
|
|
rval = MXS_AUTH_INCOMPLETE;
|
|
}
|
|
else if (auth->state == GSSAPI_AUTH_DATA_SENT)
|
|
{
|
|
/** Read authentication response */
|
|
if (mxs_mysql_is_ok_packet(buffer))
|
|
{
|
|
auth->state = GSSAPI_AUTH_OK;
|
|
rval = MXS_AUTH_SUCCEEDED;
|
|
}
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* @brief Check whether the DCB supports SSL
|
|
* @param dcb Backend DCB
|
|
* @return True if DCB supports SSL
|
|
*/
|
|
static bool gssapi_backend_auth_connectssl(DCB *dcb)
|
|
{
|
|
return dcb->server->server_ssl != NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Authenticate the backend connection
|
|
* @param dcb Backend DCB
|
|
* @return MXS_AUTH_INCOMPLETE if authentication is ongoing, MXS_AUTH_SUCCEEDED
|
|
* if authentication is complete and MXS_AUTH_FAILED if authentication failed.
|
|
*/
|
|
static int gssapi_backend_auth_authenticate(DCB *dcb)
|
|
{
|
|
int rval = MXS_AUTH_FAILED;
|
|
gssapi_auth_t *auth = (gssapi_auth_t*)dcb->authenticator_data;
|
|
|
|
if (auth->state == GSSAPI_AUTH_INIT)
|
|
{
|
|
if (send_new_auth_token(dcb))
|
|
{
|
|
rval = MXS_AUTH_INCOMPLETE;
|
|
auth->state = GSSAPI_AUTH_DATA_SENT;
|
|
}
|
|
|
|
}
|
|
else if (auth->state == GSSAPI_AUTH_OK)
|
|
{
|
|
rval = MXS_AUTH_SUCCEEDED;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* Implementation of the authenticator module interface
|
|
*/
|
|
static GWAUTHENTICATOR MyObject =
|
|
{
|
|
NULL, /* No initialize entry point */
|
|
gssapi_auth_alloc, /* Allocate authenticator data */
|
|
gssapi_backend_auth_extract, /* Extract data into structure */
|
|
gssapi_backend_auth_connectssl, /* Check if client supports SSL */
|
|
gssapi_backend_auth_authenticate, /* Authenticate user credentials */
|
|
NULL, /* Client plugin will free shared data */
|
|
gssapi_auth_free, /* Free authenticator data */
|
|
NULL /* Load users from backend databases */
|
|
};
|
|
|
|
MODULE_INFO info =
|
|
{
|
|
MODULE_API_AUTHENTICATOR,
|
|
MODULE_GA,
|
|
GWAUTHENTICATOR_VERSION,
|
|
"GSSAPI backend authenticator"
|
|
};
|
|
|
|
static char *version_str = "V1.0.0";
|
|
|
|
/**
|
|
* Version string entry point
|
|
*/
|
|
char* version()
|
|
{
|
|
return version_str;
|
|
}
|
|
|
|
/**
|
|
* Module initialization entry point
|
|
*/
|
|
void ModuleInit()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Module handle entry point
|
|
*/
|
|
GWAUTHENTICATOR* GetModuleObject()
|
|
{
|
|
return &MyObject;
|
|
}
|