Johan Wikman f0cf391c18 include/maxscale/gw.h removed.
In a subsequent change, the includes of server/core/*.c will be
cleaned up, and if there is a common set of include files, needed
by most, then a server/core/maxscale/core.h that includes those
will be introduced.
2016-10-17 12:17:09 +03:00

537 lines
15 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.
*/
/**
* @file cdc_auth.c
*
* CDC Authentication module for handling the checking of clients credentials
* in the CDC protocol.
*
* @verbatim
* Revision History
* Date Who Description
* 11/03/2016 Massimiliano Pinto Initial version
*
* @endverbatim
*/
#include <maxscale/gw_authenticator.h>
#include <sys/stat.h>
#include <cdc.h>
#include <maxscale/alloc.h>
#include <maxscale/gwdirs.h>
#include <maxscale/modutil.h>
#include <maxscale/users.h>
/* Allowed time interval (in seconds) after last update*/
#define CDC_USERS_REFRESH_TIME 30
/* Max number of load calls within the time interval */
#define CDC_USERS_REFRESH_MAX_PER_TIME 4
MODULE_INFO info =
{
MODULE_API_AUTHENTICATOR,
MODULE_GA,
GWAUTHENTICATOR_VERSION,
"The CDC client to MaxScale authenticator implementation"
};
static char *version_str = "V1.1.0";
static int cdc_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
static bool cdc_auth_is_client_ssl_capable(DCB *dcb);
static int cdc_auth_authenticate(DCB *dcb);
static void cdc_auth_free_client_data(DCB *dcb);
static int cdc_set_service_user(SERV_LISTENER *listener);
static int cdc_replace_users(SERV_LISTENER *listener);
extern char *gw_bin2hex(char *out, const uint8_t *in, unsigned int len);
extern void gw_sha1_str(const uint8_t *in, int in_len, uint8_t *out);
extern char *create_hex_sha1_sha1_passwd(char *passwd);
extern char *decryptPassword(char *crypt);
/*
* The "module object" for mysql client authenticator module.
*/
static GWAUTHENTICATOR MyObject =
{
NULL, /* No initialize entry point */
NULL, /* No create entry point */
cdc_auth_set_protocol_data, /* Extract data into structure */
cdc_auth_is_client_ssl_capable, /* Check if client supports SSL */
cdc_auth_authenticate, /* Authenticate user credentials */
cdc_auth_free_client_data, /* Free the client data held in DCB */
NULL, /* No destroy entry point */
cdc_replace_users /* Load CDC users */
};
static int cdc_auth_check(
DCB *dcb,
CDC_protocol *protocol,
char *username,
uint8_t *auth_data,
unsigned int *flags
);
static int cdc_auth_set_client_data(
CDC_session *client_data,
CDC_protocol *protocol,
uint8_t *client_auth_packet,
int client_auth_packet_size
);
/**
* Implementation of the mandatory version entry point
*
* @return version string of the module
*/
char* version()
{
return version_str;
}
/**
* The module initialisation routine, called when the module
* is first loaded.
*/
void ModuleInit()
{
}
/**
* The module entry point routine. It is this routine that
* must populate the structure that is referred to as the
* "module object", this is a structure with the set of
* external entry points for this module.
*
* @return The module object
*/
GWAUTHENTICATOR* GetModuleObject()
{
return &MyObject;
}
/**
* @brief Function to easily call authentication check.
*
* @param dcb Request handler DCB connected to the client
* @param protocol The protocol structure for the connection
* @param username String containing username
* @param auth_data The encrypted password for authentication
* @return Authentication status
* @note Authentication status codes are defined in cdc.h
*/
static int cdc_auth_check(DCB *dcb, CDC_protocol *protocol, char *username, uint8_t *auth_data,
unsigned int *flags)
{
if (dcb->listener->users)
{
char *user_password = users_fetch(dcb->listener->users, username);
if (user_password)
{
/* compute SHA1 of auth_data */
uint8_t sha1_step1[SHA_DIGEST_LENGTH] = "";
char hex_step1[2 * SHA_DIGEST_LENGTH + 1] = "";
gw_sha1_str(auth_data, SHA_DIGEST_LENGTH, sha1_step1);
gw_bin2hex(hex_step1, sha1_step1, SHA_DIGEST_LENGTH);
return memcmp(user_password, hex_step1, SHA_DIGEST_LENGTH) == 0 ?
CDC_STATE_AUTH_OK : CDC_STATE_AUTH_FAILED;
}
}
return CDC_STATE_AUTH_FAILED;
}
/**
* @brief Authenticates a CDC user who is a client to MaxScale.
*
* @param dcb Request handler DCB connected to the client
* @return Authentication status
* @note Authentication status codes are defined in cdc.h
*/
static int
cdc_auth_authenticate(DCB *dcb)
{
CDC_protocol *protocol = DCB_PROTOCOL(dcb, CDC_protocol);
CDC_session *client_data = (CDC_session *)dcb->data;
int auth_ret;
if (0 == strlen(client_data->user))
{
auth_ret = CDC_STATE_AUTH_ERR;
}
else
{
MXS_DEBUG("Receiving connection from '%s'",
client_data->user);
auth_ret = cdc_auth_check(dcb, protocol, client_data->user, client_data->auth_data, client_data->flags);
/* On failed authentication try to reload users and authenticate again */
if (CDC_STATE_AUTH_OK != auth_ret && cdc_replace_users(dcb->listener) == MXS_AUTH_LOADUSERS_OK)
{
auth_ret = cdc_auth_check(dcb, protocol, client_data->user, client_data->auth_data, client_data->flags);
}
/* on successful authentication, set user into dcb field */
if (CDC_STATE_AUTH_OK == auth_ret)
{
dcb->user = MXS_STRDUP_A(client_data->user);
}
else if (dcb->service->log_auth_warnings)
{
MXS_NOTICE("%s: login attempt for user '%s', authentication failed.",
dcb->service->name, client_data->user);
}
}
return auth_ret;
}
/**
* @brief Transfer data from the authentication request to the DCB.
*
* The request handler DCB has a field called data that contains protocol
* specific information. This function examines a buffer containing CDC
* authentication data and puts it into a structure that is referred to
* by the DCB. If the information in the buffer is invalid, then a failure
* code is returned. A call to cdc_auth_set_client_data does the
* detailed work.
*
* @param dcb Request handler DCB connected to the client
* @param buffer Pointer to pointer to buffer containing data from client
* @return Authentication status
* @note Authentication status codes are defined in cdc.h
*/
static int
cdc_auth_set_protocol_data(DCB *dcb, GWBUF *buf)
{
uint8_t *client_auth_packet = GWBUF_DATA(buf);
CDC_protocol *protocol = NULL;
CDC_session *client_data = NULL;
int client_auth_packet_size = 0;
protocol = DCB_PROTOCOL(dcb, CDC_protocol);
CHK_PROTOCOL(protocol);
if (dcb->data == NULL)
{
if (NULL == (client_data = (CDC_session *)MXS_CALLOC(1, sizeof(CDC_session))))
{
return CDC_STATE_AUTH_ERR;
}
dcb->data = client_data;
}
else
{
client_data = (CDC_session *)dcb->data;
}
client_auth_packet_size = gwbuf_length(buf);
return cdc_auth_set_client_data(client_data, protocol, client_auth_packet,
client_auth_packet_size);
}
/**
* @brief Transfer detailed data from the authentication request to the DCB.
*
* The caller has created the data structure pointed to by the DCB, and this
* function fills in the details. If problems are found with the data, the
* return code indicates failure.
*
* @param client_data The data structure for the DCB
* @param protocol The protocol structure for this connection
* @param client_auth_packet The data from the buffer received from client
* @param client_auth_packet size An integer giving the size of the data
* @return Authentication status
* @note Authentication status codes are defined in cdc.h
*/
static int
cdc_auth_set_client_data(CDC_session *client_data,
CDC_protocol *protocol,
uint8_t *client_auth_packet,
int client_auth_packet_size)
{
if (client_auth_packet_size % 2 != 0)
{
/** gw_hex2bin expects an even number of bytes */
client_auth_packet_size--;
}
int rval = CDC_STATE_AUTH_ERR;
int decoded_size = client_auth_packet_size / 2;
char decoded_buffer[decoded_size + 1]; // Extra for terminating null
/* decode input data */
if (client_auth_packet_size <= CDC_USER_MAXLEN)
{
gw_hex2bin((uint8_t*)decoded_buffer, (const char *)client_auth_packet,
client_auth_packet_size);
decoded_buffer[decoded_size] = '\0';
char *tmp_ptr = strchr(decoded_buffer, ':');
if (tmp_ptr)
{
size_t user_len = tmp_ptr - decoded_buffer;
*tmp_ptr++ = '\0';
size_t auth_len = decoded_size - (tmp_ptr - decoded_buffer);
if (user_len <= CDC_USER_MAXLEN && auth_len == SHA_DIGEST_LENGTH)
{
strcpy(client_data->user, decoded_buffer);
memcpy(client_data->auth_data, tmp_ptr, auth_len);
rval = CDC_STATE_AUTH_OK;
}
}
else
{
MXS_ERROR("Authentication failed, the decoded client authentication "
"packet is malformed. Expected <username>:SHA1(<password>)");
}
}
else
{
MXS_ERROR("Authentication failed, client authentication packet length "
"exceeds the maximum allowed length of %d bytes.", CDC_USER_MAXLEN);
}
return rval;
}
/**
* @brief Determine whether the client is SSL capable
*
* The authentication request from the client will indicate whether the client
* is expecting to make an SSL connection. The information has been extracted
* in the previous functions.
*
* @param dcb Request handler DCB connected to the client
* @return Boolean indicating whether client is SSL capable
*/
static bool
cdc_auth_is_client_ssl_capable(DCB *dcb)
{
return false;
}
/**
* @brief Free the client data pointed to by the passed DCB.
*
* Currently all that is required is to free the storage pointed to by
* dcb->data. But this is intended to be implemented as part of the
* authentication API at which time this code will be moved into the
* CDC authenticator. If the data structure were to become more complex
* the mechanism would still work and be the responsibility of the authenticator.
* The DCB should not know authenticator implementation details.
*
* @param dcb Request handler DCB connected to the client
*/
static void
cdc_auth_free_client_data(DCB *dcb)
{
MXS_FREE(dcb->data);
}
/*
* Add the service user to CDC dbusers (service->users)
* via cdc_users_alloc
*
* @param service The current service
* @return 0 on success, 1 on failure
*/
static int
cdc_set_service_user(SERV_LISTENER *listener)
{
SERVICE *service = listener->service;
char *dpwd = NULL;
char *newpasswd = NULL;
char *service_user = NULL;
char *service_passwd = NULL;
if (serviceGetUser(service, &service_user, &service_passwd) == 0)
{
MXS_ERROR("failed to get service user details for service %s",
service->name);
return 1;
}
dpwd = decryptPassword(service->credentials.authdata);
if (!dpwd)
{
MXS_ERROR("decrypt password failed for service user %s, service %s",
service_user,
service->name);
return 1;
}
newpasswd = create_hex_sha1_sha1_passwd(dpwd);
if (!newpasswd)
{
MXS_ERROR("create hex_sha1_sha1_password failed for service user %s",
service_user);
MXS_FREE(dpwd);
return 1;
}
/* add service user */
(void)users_add(listener->users, service->credentials.name, newpasswd);
MXS_FREE(newpasswd);
MXS_FREE(dpwd);
return 0;
}
/**
* Load the AVRO users
*
* @param service Current service
* @param usersfile File with users
* @return -1 on error or users loaded (including 0)
*/
static int
cdc_read_users(USERS *users, char *usersfile)
{
FILE *fp;
int loaded = 0;
char *avro_user;
char *user_passwd;
/* user maxlen ':' password hash '\n' '\0' */
char read_buffer[CDC_USER_MAXLEN + 1 + SHA_DIGEST_LENGTH + 1 + 1];
char *all_users_data = NULL;
struct stat statb;
int fd;
int filelen = 0;
unsigned char hash[SHA_DIGEST_LENGTH] = "";
int max_line_size = sizeof(read_buffer) - 1;
if ((fp = fopen(usersfile, "r")) == NULL)
{
return -1;
}
fd = fileno(fp);
if (fstat(fd, &statb) == 0)
{
filelen = statb.st_size;
}
if ((all_users_data = MXS_MALLOC(filelen + 1)) == NULL)
{
return -1;
}
*all_users_data = '\0';
while (!feof(fp))
{
if (fgets(read_buffer, max_line_size, fp) != NULL)
{
char *tmp_ptr = read_buffer;
/* append data for hash */
strcat(all_users_data, read_buffer);
if ((tmp_ptr = strchr(read_buffer, ':')) != NULL)
{
*tmp_ptr++ = '\0';
avro_user = read_buffer;
user_passwd = tmp_ptr;
if ((tmp_ptr = strchr(user_passwd, '\n')) != NULL)
{
*tmp_ptr = '\0';
}
/* add user */
users_add(users, avro_user, user_passwd);
loaded++;
}
}
}
/* compute SHA1 digest for users' data */
SHA1((const unsigned char *) all_users_data, strlen(all_users_data), hash);
memcpy(users->cksum, hash, SHA_DIGEST_LENGTH);
MXS_FREE(all_users_data);
fclose(fp);
return loaded;
}
/**
* @brief Replace the user/passwd in the servicei users tbale from a db file
*
* @param service The current service
*/
int cdc_replace_users(SERV_LISTENER *listener)
{
int rc = MXS_AUTH_LOADUSERS_ERROR;
USERS *newusers = users_alloc();
if (newusers)
{
char path[PATH_MAX + 1];
snprintf(path, PATH_MAX, "%s/%s/cdcusers", get_datadir(), listener->service->name);
int i = cdc_read_users(newusers, path);
USERS *oldusers = NULL;
spinlock_acquire(&listener->lock);
if (i > 0)
{
/** Successfully loaded at least one user */
oldusers = listener->users;
listener->users = newusers;
rc = MXS_AUTH_LOADUSERS_OK;
}
else if (listener->users)
{
/** Failed to load users, use the old users table */
users_free(newusers);
}
else
{
/** No existing users, use the new empty users table */
listener->users = newusers;
}
cdc_set_service_user(listener);
spinlock_release(&listener->lock);
if (oldusers)
{
users_free(oldusers);
}
}
return rc;
}