Commit 55d61ec8 authored by Vladislav Vaintroub's avatar Vladislav Vaintroub

MDEV-4961 SSPI/GSSAPI/Kerberos authentication plugin

parent 111acb72
IF (WIN32)
SET(USE_SSPI 1)
ENDIF()
IF(USE_SSPI)
SET(GSSAPI_LIBS secur32)
ADD_DEFINITIONS(-DPLUGIN_SSPI)
SET(GSSAPI_CLIENT sspi_client.cc)
SET(GSSAPI_SERVER sspi_server.cc)
SET(GSSAPI_ERRMSG sspi_errmsg.cc)
ELSE()
SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
FIND_PACKAGE(GSSAPI)
IF(GSSAPI_FOUND)
INCLUDE_DIRECTORIES(${GSSAPI_INCS})
ADD_DEFINITIONS(-DPLUGIN_GSSAPI)
SET(GSSAPI_CLIENT gssapi_client.cc)
SET(GSSAPI_SERVER gssapi_server.cc)
SET(GSSAPI_ERRMSG gssapi_errmsg.cc)
ELSE()
# Can't build plugin
RETURN()
ENDIF()
ENDIF ()
MYSQL_ADD_PLUGIN(auth_gssapi server_plugin.cc ${GSSAPI_SERVER} ${GSSAPI_ERRMSG}
LINK_LIBRARIES ${GSSAPI_LIBS}
MODULE_ONLY)
MYSQL_ADD_PLUGIN(auth_gssapi_client client_plugin.cc ${GSSAPI_CLIENT} ${GSSAPI_ERRMSG}
LINK_LIBRARIES ${GSSAPI_LIBS}
MODULE_ONLY)
# GSSAPI/SSPI authentication for MariaDB
This article gives instructions on configuring GSSAPI authentication plugin
for MariaDB for passwordless login.
On Unix systems, GSSAPI is usually synonymous with Kerberos authentication.
Windows has slightly different but very similar API called SSPI, that along with Kerberos, also supports NTLM authentication.
This plugin includes support for Kerberos on Unix, but also can be used as for Windows authentication with or without domain
environment.
## Server-side preparations on Unix
To use the plugin, some preparation need to be done on the server side on Unixes.
MariaDB server will read need access to the Kerberos keytab file, that contains service principal name for the MariaDB server.
If you are using **Unix Kerberos KDC (MIT,Heimdal)**
- Create service principal using kadmin tool
```
kadmin -q "addprinc -randkey mariadb/host.domain.com"
```
(replace host.domain.com with fully qualified DNS name for the server host)
- Export the newly created user to the keytab file
```
kadmin -q "ktadd -k /path/to/mariadb.keytab mariadb/host.domain.com"
```
More details can be found [here](http://www.microhowto.info/howto/create_a_service_principal_using_mit_kerberos.html)
and [here](http://www.microhowto.info/howto/add_a_host_or_service_principal_to_a_keytab_using_mit_kerberos.html)
If you are using **Windows Active Directory KDC**
you can need to create keytab using ktpass.exe tool on Windows, map principal user to an existing domain user like this
```
ktpass.exe /princ mariadb/host.domain.com@DOMAIN.COM /mapuser someuser /pass MyPas$w0rd /out mariadb.keytab /crypto all /ptype KRB5_NT_PRINCIPAL /mapop set
```
and then transfer the keytab file to the Unix server. See [Microsoft documentation](https://technet.microsoft.com/en-us/library/cc753771.aspx) for details.
## Server side preparations on Windows.
Usually nothing need to be done. MariaDB server should to run on a domain joined machine, either as NetworkService account
(which is default if it runs as service) or run under any other domain account credentials.
Creating service principal is not required here (but you can still do it using [_setspn_](https://technet.microsoft.com/en-us/library/cc731241.aspx) tool)
# Installing plugin
- Start the server
- On Unix, edit my the my.cnf/my.ini configuration file, set the parameter gssapi-keytab-path to point to previously
created keytab path.
```
gssapi-keytab-path=/path/to/mariadb.keytab
```
- Optionally on Unix, in case the service principal name differs from default mariadb/host.domain.com@REALM,
configure alternative principal name with
```
gssapi-principal-name=alternative/principalname@REALM
```
- In mysql command line client, execute
```
INSTALL SONAME 'auth_gssapi'
```
#Creating users
Now, you can create a user for GSSAPI/SSPI authentication. CREATE USER command, for Kerberos user
would be like this (*long* form, see below for short one)
```
CREATE USER usr1 IDENTIFIED WITH gssapi AS 'usr1@EXAMPLE.COM';
```
(replace with real username and realm)
The part after AS is mechanism specific, and needs to be ``machine\\usr1`` for Windows users identified with NTLM.
You may also use alternative *short* form of CREATE USER
```
CREATE USER usr1 IDENTIFIED WITH gssapi;
```
If this syntax is used, realm part is used for comparison
thus 'usr1@EXAMPLE.COM', 'usr1@EXAMPLE.CO.UK' and 'mymachine\usr1' will all identify as 'usr1'.
#Login as GSSAPI user with command line clients
Using command line client, do
```
mysql --plugin-dir=/path/to/plugin-dir -u usr1
```
#Plugin variables
- **gssapi-keytab-path** (Unix only) - Path to the server keytab file
- **gssapi-principal-name** - name of the service principal.
- **gssapi-mech-name** (Windows only) - Name of the SSPI package used by server. Can be either 'Kerberos' or 'Negotiate'.
Defaults to 'Negotiate' (both Kerberos and NTLM users can connect)
Set it to 'Kerberos', to prevent less secure NTLM in domain environments, but leave it as default(Negotiate)
to allow non-domain environment (e.g if server does not run in domain enviroment).
#Implementation
Overview of the protocol between client and server
1. Server : Construct gssapi-principal-name if not set in my.cnf. On Unixes defaults to hostbased name for service "mariadb". On Windows to user's or machine's domain names.
Acquire credentials for gssapi-principal-name with ```gss_acquire_cred() / AcquireSecurityCredentials()```.
Send packet with principal name and mech ```"gssapi-principal-name\0gssapi-mech-name\0"``` to client ( on Unix, empty string used for gssapi-mech)
2. Client: execute ```gss_init_sec_context() / InitializeSecurityContext()``` passing gssapi-principal-name / gssapi-mech-name parameters.
Send resulting GSSAPI blob to server.
3. Server : receive blob from client, execute ```gss_accept_sec_context()/ AcceptSecurityContext()```, send resulting blob back to client
4. Perform 2. and 3. can until both client and server decide that authentication is done, or until some error occured. If authentication was successful, GSSAPI context (an opaque structure) is generated on both client and server sides.
5. Server : Client name is extracted from the context, and compared to the name provided by client(with or without realm). If name matches, plugin returns success.
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/**
@file
GSSAPI authentication plugin, client side
*/
#include <string.h>
#include <stdarg.h>
#include <mysqld_error.h>
#include <mysql/client_plugin.h>
#include <mysql.h>
#include <stdio.h>
#include "common.h"
extern int auth_client(char *principal_name,
char *mech,
MYSQL *mysql,
MYSQL_PLUGIN_VIO *vio);
static void parse_server_packet(char *packet, size_t packet_len, char *spn, char *mech)
{
size_t spn_len;
spn_len = strnlen(packet, packet_len);
strncpy(spn, packet, PRINCIPAL_NAME_MAX);
if (spn_len == packet_len - 1)
{
/* Mechanism not included into packet */
*mech = 0;
}
else
{
strncpy(mech, packet + spn_len + 1, MECH_NAME_MAX);
}
}
/**
Set client error message.
*/
void log_client_error(MYSQL *mysql, const char *format, ...)
{
NET *net= &mysql->net;
va_list args;
net->last_errno= ER_UNKNOWN_ERROR;
va_start(args, format);
vsnprintf(net->last_error, sizeof(net->last_error) - 1,
format, args);
va_end(args);
memcpy(net->sqlstate, "HY000", sizeof(net->sqlstate));
}
/**
The main client function of the GSSAPI plugin.
*/
static int gssapi_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
{
int packet_len;
unsigned char *packet;
char spn[PRINCIPAL_NAME_MAX + 1];
char mech[MECH_NAME_MAX + 1];
/* read from server for service principal name */
packet_len= vio->read_packet(vio, &packet);
if (packet_len < 0)
{
return CR_ERROR;
}
parse_server_packet((char *)packet, (size_t)packet_len, spn, mech);
return auth_client(spn, mech, mysql, vio);
}
/* register client plugin */
mysql_declare_client_plugin(AUTHENTICATION)
"auth_gssapi_client",
"Shuang Qiu, Robbie Harwood, Vladislav Vaintroub",
"GSSAPI/SSPI based authentication",
{0, 1, 0},
"BSD",
NULL,
NULL,
NULL,
NULL,
gssapi_auth_client
mysql_end_client_plugin;
# - Try to detect the GSSAPI support
# Once done this will define
#
# GSSAPI_FOUND - system supports GSSAPI
# GSSAPI_INCS - the GSSAPI include directory
# GSSAPI_LIBS - the libraries needed to use GSSAPI
# GSSAPI_FLAVOR - the type of API - MIT or HEIMDAL
# Copyright (c) 2006, Pino Toscano, <toscano.pino@tiscali.it>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
if(GSSAPI_LIBS AND GSSAPI_FLAVOR)
# in cache already
set(GSSAPI_FOUND TRUE)
else(GSSAPI_LIBS AND GSSAPI_FLAVOR)
find_program(KRB5_CONFIG NAMES krb5-config PATHS
/opt/local/bin
ONLY_CMAKE_FIND_ROOT_PATH # this is required when cross compiling with cmake 2.6 and ignored with cmake 2.4, Alex
)
mark_as_advanced(KRB5_CONFIG)
#reset vars
set(GSSAPI_INCS)
set(GSSAPI_LIBS)
set(GSSAPI_FLAVOR)
if(KRB5_CONFIG)
set(HAVE_KRB5_GSSAPI TRUE)
exec_program(${KRB5_CONFIG} ARGS --libs gssapi RETURN_VALUE _return_VALUE OUTPUT_VARIABLE GSSAPI_LIBS)
if(_return_VALUE)
message(STATUS "GSSAPI configure check failed.")
set(HAVE_KRB5_GSSAPI FALSE)
endif(_return_VALUE)
exec_program(${KRB5_CONFIG} ARGS --cflags gssapi RETURN_VALUE _return_VALUE OUTPUT_VARIABLE GSSAPI_INCS)
string(REGEX REPLACE "(\r?\n)+$" "" GSSAPI_INCS "${GSSAPI_INCS}")
string(REGEX REPLACE " *-I" ";" GSSAPI_INCS "${GSSAPI_INCS}")
exec_program(${KRB5_CONFIG} ARGS --vendor RETURN_VALUE _return_VALUE OUTPUT_VARIABLE gssapi_flavor_tmp)
set(GSSAPI_FLAVOR_MIT)
if(gssapi_flavor_tmp MATCHES ".*Massachusetts.*")
set(GSSAPI_FLAVOR "MIT")
else(gssapi_flavor_tmp MATCHES ".*Massachusetts.*")
set(GSSAPI_FLAVOR "HEIMDAL")
endif(gssapi_flavor_tmp MATCHES ".*Massachusetts.*")
if(NOT HAVE_KRB5_GSSAPI)
if (gssapi_flavor_tmp MATCHES "Sun Microsystems.*")
message(STATUS "Solaris Kerberos does not have GSSAPI; this is normal.")
set(GSSAPI_LIBS)
set(GSSAPI_INCS)
else(gssapi_flavor_tmp MATCHES "Sun Microsystems.*")
message(WARNING "${KRB5_CONFIG} failed unexpectedly.")
endif(gssapi_flavor_tmp MATCHES "Sun Microsystems.*")
endif(NOT HAVE_KRB5_GSSAPI)
if(GSSAPI_LIBS) # GSSAPI_INCS can be also empty, so don't rely on that
set(GSSAPI_FOUND TRUE CACHE STRING "")
message(STATUS "Found GSSAPI: ${GSSAPI_LIBS}")
set(GSSAPI_INCS ${GSSAPI_INCS} CACHE STRING "")
set(GSSAPI_LIBS ${GSSAPI_LIBS} CACHE STRING "")
set(GSSAPI_FLAVOR ${GSSAPI_FLAVOR} CACHE STRING "")
mark_as_advanced(GSSAPI_INCS GSSAPI_LIBS GSSAPI_FLAVOR)
endif(GSSAPI_LIBS)
endif(KRB5_CONFIG)
endif(GSSAPI_LIBS AND GSSAPI_FLAVOR)
\ No newline at end of file
/** Maximal length of the target name */
#define PRINCIPAL_NAME_MAX 256
/** Maximal length of the mech string */
#define MECH_NAME_MAX 30
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <gssapi/gssapi.h>
#include <string.h>
#include <stdio.h>
#include <mysql/plugin_auth.h>
#include <mysqld_error.h>
#include <mysql.h>
#include "gssapi_errmsg.h"
extern void log_client_error(MYSQL *mysql,const char *fmt,...);
/* This sends the error to the client */
static void log_error(MYSQL *mysql, OM_uint32 major, OM_uint32 minor, const char *msg)
{
if (GSS_ERROR(major))
{
char sysmsg[1024];
gssapi_errmsg(major, minor, sysmsg, sizeof(sysmsg));
log_client_error(mysql,
"Client GSSAPI error (major %u, minor %u) : %s - %s",
major, minor, msg, sysmsg);
}
else
{
log_client_error(mysql, "Client GSSAPI error : %s", msg);
}
}
int auth_client(char *principal_name, char *mech, MYSQL *mysql, MYSQL_PLUGIN_VIO *vio)
{
int ret= CR_ERROR;
OM_uint32 major= 0, minor= 0;
gss_ctx_id_t ctxt= GSS_C_NO_CONTEXT;
gss_name_t service_name= GSS_C_NO_NAME;
if (principal_name && principal_name[0])
{
/* import principal from plain text */
gss_buffer_desc principal_name_buf;
principal_name_buf.length= strlen(principal_name);
principal_name_buf.value= (void *) principal_name;
major= gss_import_name(&minor, &principal_name_buf, GSS_C_NT_USER_NAME, &service_name);
if (GSS_ERROR(major))
{
log_error(mysql, major, minor, "gss_import_name");
return CR_ERROR;
}
}
gss_buffer_desc input= {0,0};
do
{
gss_buffer_desc output= {0,0};
major= gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &ctxt, service_name,
GSS_C_NO_OID, 0, 0, GSS_C_NO_CHANNEL_BINDINGS,
&input, NULL, &output, NULL, NULL);
if (output.length)
{
/* send credential */
if(vio->write_packet(vio, (unsigned char *)output.value, output.length))
{
/* Server error packet contains detailed message. */
ret= CR_OK_HANDSHAKE_COMPLETE;
gss_release_buffer (&minor, &output);
goto cleanup;
}
}
gss_release_buffer (&minor, &output);
if (GSS_ERROR(major))
{
log_error(mysql, major, minor,"gss_init_sec_context");
goto cleanup;
}
if (major & GSS_S_CONTINUE_NEEDED)
{
int len= vio->read_packet(vio, (unsigned char **) &input.value);
if (len <= 0)
{
/* Server error packet contains detailed message. */
ret= CR_OK_HANDSHAKE_COMPLETE;
goto cleanup;
}
input.length= len;
}
} while (major & GSS_S_CONTINUE_NEEDED);
ret= CR_OK;
cleanup:
if (service_name != GSS_C_NO_NAME)
gss_release_name(&minor, &service_name);
if (ctxt != GSS_C_NO_CONTEXT)
gss_delete_sec_context(&minor, &ctxt, GSS_C_NO_BUFFER);
return ret;
}
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <gssapi.h>
#include <string.h>
void gssapi_errmsg(OM_uint32 major, OM_uint32 minor, char *buf, size_t size)
{
OM_uint32 message_context;
OM_uint32 status_code;
OM_uint32 maj_status;
OM_uint32 min_status;
gss_buffer_desc status_string;
char *p= buf;
char *end= buf + size - 1;
int types[] = {GSS_C_GSS_CODE,GSS_C_MECH_CODE};
for(int i= 0; i < 2;i++)
{
message_context= 0;
status_code= types[i] == GSS_C_GSS_CODE?major:minor;
if(!status_code)
continue;
do
{
maj_status = gss_display_status(
&min_status,
status_code,
types[i],
GSS_C_NO_OID,
&message_context,
&status_string);
if(maj_status)
break;
if(p + status_string.length + 2 < end)
{
memcpy(p,status_string.value, status_string.length);
p += status_string.length;
*p++ = '.';
*p++ = ' ';
}
gss_release_buffer(&min_status, &status_string);
}
while (message_context != 0);
}
*p= 0;
}
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
extern void gssapi_errmsg(OM_uint32 major, OM_uint32 minor, char *buf, size_t size);
#include <my_config.h>
#include <gssapi/gssapi.h>
#include <stdio.h>
#include <mysql/plugin_auth.h>
#include <my_sys.h>
#include <mysqld_error.h>
#include <log.h>
#include "server_plugin.h"
#include "gssapi_errmsg.h"
static gss_name_t service_name = GSS_C_NO_NAME;
/* This sends the error to the client */
static void log_error( OM_uint32 major, OM_uint32 minor, const char *msg)
{
if (GSS_ERROR(major))
{
char sysmsg[1024];
gssapi_errmsg(major, minor, sysmsg, sizeof(sysmsg));
my_printf_error(ER_UNKNOWN_ERROR,"Server GSSAPI error (major %u, minor %u) : %s -%s",
MYF(0), major, minor, msg, sysmsg);
}
else
{
my_printf_error(ER_UNKNOWN_ERROR, "Server GSSAPI error : %s", MYF(0), msg);
}
}
/*
Generate default principal service name formatted as principal name "mariadb/server.fqdn@REALM"
*/
#include <krb5.h>
static char* get_default_principal_name()
{
static char default_name[1024];
char *unparsed_name= NULL;
krb5_context context= NULL;
krb5_principal principal= NULL;
krb5_keyblock *key= NULL;
if(krb5_init_context(&context))
{
sql_print_warning("GSSAPI plugin : krb5_init_context failed");
goto cleanup;
}
if (krb5_sname_to_principal(context, NULL, "mariadb", KRB5_NT_SRV_HST, &principal))
{
sql_print_warning("GSSAPI plugin : krb5_sname_to_principal failed");
goto cleanup;
}
if (krb5_unparse_name(context, principal, &unparsed_name))
{
sql_print_warning("GSSAPI plugin : krb5_unparse_name failed");
goto cleanup;
}
/* Check for entry in keytab */
if (krb5_kt_read_service_key(context, NULL, principal, 0, 0, &key))
{
sql_print_warning("GSSAPI plugin : default principal '%s' not found in keytab", unparsed_name);
goto cleanup;
}
strncpy(default_name, unparsed_name, sizeof(default_name)-1);
cleanup:
if (key)
krb5_free_keyblock(context, key);
if (unparsed_name)
krb5_free_unparsed_name(context, unparsed_name);
if (principal)
krb5_free_principal(context, principal);
if (context)
krb5_free_context(context);
return default_name;
}
int plugin_init()
{
gss_buffer_desc principal_name_buf;
OM_uint32 major= 0, minor= 0;
gss_cred_id_t cred= GSS_C_NO_CREDENTIAL;
if(srv_keytab_path && srv_keytab_path[0])
{
setenv("KRB5_KTNAME", srv_keytab_path, 1);
}
if(!srv_principal_name || !srv_principal_name[0])
srv_principal_name= get_default_principal_name();
/* import service principal from plain text */
if(srv_principal_name && srv_principal_name[0])
{
sql_print_information("GSSAPI plugin : using principal name '%s'", srv_principal_name);
principal_name_buf.length= strlen(srv_principal_name);
principal_name_buf.value= srv_principal_name;
major= gss_import_name(&minor, &principal_name_buf, GSS_C_NT_USER_NAME, &service_name);
if(GSS_ERROR(major))
{
log_error(major, minor, "gss_import_name");
return -1;
}
}
else
{
service_name= GSS_C_NO_NAME;
}
/* Check if SPN configuration is OK */
major= gss_acquire_cred(&minor, service_name, GSS_C_INDEFINITE,
GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cred, NULL,
NULL);
if (GSS_ERROR(major))
{
log_error(major, minor, "gss_acquire_cred failed");
return -1;
}
gss_release_cred(&minor, &cred);
return 0;
}
int plugin_deinit()
{
if (service_name != GSS_C_NO_NAME)
{
OM_uint32 minor;
gss_release_name(&minor, &service_name);
}
return 0;
}
int auth_server(MYSQL_PLUGIN_VIO *vio,const char *user, size_t userlen, int use_full_name)
{
int rc= CR_ERROR; /* return code */
/* GSSAPI related fields */
OM_uint32 major= 0, minor= 0, flags= 0;
gss_cred_id_t cred= GSS_C_NO_CREDENTIAL; /* credential identifier */
gss_ctx_id_t ctxt= GSS_C_NO_CONTEXT; /* context identifier */
gss_name_t client_name;
gss_buffer_desc client_name_buf, input, output;
char *client_name_str;
/* server acquires credential */
major= gss_acquire_cred(&minor, service_name, GSS_C_INDEFINITE,
GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cred, NULL,
NULL);
if (GSS_ERROR(major))
{
log_error(major, minor, "gss_acquire_cred failed");
goto cleanup;
}
input.length= 0;
input.value= NULL;
do
{
/* receive token from peer */
int len= vio->read_packet(vio, (unsigned char **) &input.value);
if (len < 0)
{
log_error(0, 0, "fail to read token from client");
goto cleanup;
}
input.length= len;
major= gss_accept_sec_context(&minor, &ctxt, cred, &input,
GSS_C_NO_CHANNEL_BINDINGS, &client_name,
NULL, &output, &flags, NULL, NULL);
if (GSS_ERROR(major))
{
log_error(major, minor, "gss_accept_sec_context");
rc= CR_ERROR;
goto cleanup;
}
/* send token to peer */
if (output.length)
{
if (vio->write_packet(vio, (const uchar *) output.value, output.length))
{
gss_release_buffer(&minor, &output);
log_error(major, minor, "communication error(write)");
goto cleanup;
}
gss_release_buffer(&minor, &output);
}
} while (major & GSS_S_CONTINUE_NEEDED);
/* extract plain text client name */
major= gss_display_name(&minor, client_name, &client_name_buf, NULL);
if (GSS_ERROR(major))
{
log_error(major, minor, "gss_display_name");
goto cleanup;
}
client_name_str= (char *)client_name_buf.value;
/*
* Compare input user name with the actual one. Return success if
* the names match exactly, or if use_full_name parameter is not set
* up to the '@' separator.
*/
if ((userlen == client_name_buf.length) ||
(!use_full_name
&& userlen < client_name_buf.length
&& client_name_str[userlen] == '@'))
{
if (strncmp(client_name_str, user, userlen) == 0)
{
rc= CR_OK;
}
}
if(rc != CR_OK)
{
my_printf_error(ER_ACCESS_DENIED_ERROR,
"GSSAPI name mismatch, requested '%s', actual name '%.*s'",
MYF(0), user, (int)client_name_buf.length, client_name_str);
}
gss_release_buffer(&minor, &client_name_buf);
cleanup:
if (ctxt != GSS_C_NO_CONTEXT)
gss_delete_sec_context(&minor, &ctxt, GSS_C_NO_BUFFER);
if (cred != GSS_C_NO_CREDENTIAL)
gss_release_cred(&minor, &cred);
return(rc);
}
INSTALL SONAME 'auth_gssapi';
CREATE USER GSSAPI_SHORTNAME IDENTIFIED WITH gssapi;
SELECT USER(),CURRENT_USER();
USER() CURRENT_USER()
GSSAPI_SHORTNAME@localhost GSSAPI_SHORTNAME@%
DROP USER GSSAPI_SHORTNAME;
CREATE USER nosuchuser IDENTIFIED WITH gssapi;
ERROR 28000: GSSAPI name mismatch, requested 'nosuchuser', actual name 'GSSAPI_SHORTNAME'
DROP USER nosuchuser;
CREATE USER usr1 IDENTIFIED WITH gssapi as 'GSSAPI_FULLNAME';
SELECT USER(),CURRENT_USER();
USER() CURRENT_USER()
usr1@localhost usr1@%
DROP USER usr1;
CREATE USER nosuchuser IDENTIFIED WITH gssapi AS 'nosuchuser@EXAMPLE.COM';
ERROR 28000: GSSAPI name mismatch, requested 'nosuchuser@EXAMPLE.COM', actual name 'GSSAPI_FULLNAME'
DROP USER nosuchuser;
UNINSTALL SONAME 'auth_gssapi';
INSTALL SONAME 'auth_gssapi';
#
# CREATE USER without 'AS' clause
#
--replace_result $GSSAPI_SHORTNAME GSSAPI_SHORTNAME
eval CREATE USER $GSSAPI_SHORTNAME IDENTIFIED WITH gssapi;
connect (con1,localhost,$GSSAPI_SHORTNAME,,);
--replace_result $GSSAPI_SHORTNAME GSSAPI_SHORTNAME
SELECT USER(),CURRENT_USER();
disconnect con1;
connection default;
--replace_result $GSSAPI_SHORTNAME GSSAPI_SHORTNAME
eval DROP USER $GSSAPI_SHORTNAME;
CREATE USER nosuchuser IDENTIFIED WITH gssapi;
--disable_query_log
--replace_regex /actual name '.*'/actual name 'GSSAPI_SHORTNAME'/
--error ER_ACCESS_DENIED_ERROR
connect (con1,localhost,nosuchuser,,);
--enable_query_log
DROP USER nosuchuser;
#
# CREATE USER with 'AS' clause
#
--replace_result $GSSAPI_FULLNAME GSSAPI_FULLNAME
eval CREATE USER usr1 IDENTIFIED WITH gssapi as '$GSSAPI_FULLNAME';
connect (con1,localhost,usr1,,);
--replace_result $GSSAPI_FULLNAME GSSAPI_FULLNAME
SELECT USER(),CURRENT_USER();
disconnect con1;
connection default;
DROP USER usr1;
CREATE USER nosuchuser IDENTIFIED WITH gssapi AS 'nosuchuser@EXAMPLE.COM';
--disable_query_log
--replace_regex /actual name '.*'/actual name 'GSSAPI_FULLNAME'/
--error ER_ACCESS_DENIED_ERROR
connect (con1,localhost,nosuchuser,,);
--enable_query_log
DROP USER nosuchuser;
UNINSTALL SONAME 'auth_gssapi';
\ No newline at end of file
--loose-gssapi-keytab-path=$GSSAPI_KEYTAB_PATH --loose-gssapi-principal-name=$GSSAPI_PRINCIPAL_NAME
package My::Suite::AuthGSSAPI;
@ISA = qw(My::Suite);
return "No AUTH_GSSAPI plugin" unless $ENV{AUTH_GSSAPI_SO};
return "Not run for embedded server" if $::opt_embedded_server;
# Following environment variables may need to be set
if ($^O eq "MSWin32")
{
chomp(my $whoami =`whoami /UPN 2>NUL` || `whoami`);
my $fullname = $whoami;
$fullname =~ s/\\/\\\\/; # SQL escaping for backslash
$ENV{'GSSAPI_FULLNAME'} = $fullname;
$ENV{'GSSAPI_SHORTNAME'} = $ENV{'USERNAME'};
}
else
{
if (!$ENV{'GSSAPI_FULLNAME'})
{
my $s = `klist |grep 'Default principal: '`;
if ($s)
{
chomp($s);
my $fullname = substr($s,19);
$ENV{'GSSAPI_FULLNAME'} = $fullname;
}
}
$ENV{'GSSAPI_SHORTNAME'} = (split /@/, $ENV{'GSSAPI_FULLNAME'}) [0];
}
if (!$ENV{'GSSAPI_FULLNAME'} || !$ENV{'GSSAPI_SHORTNAME'})
{
return "Environment variable GSSAPI_SHORTNAME and GSSAPI_FULLNAME need to be set"
}
foreach $var ('GSSAPI_SHORTNAME','GSSAPI_FULLNAME','GSSAPI_KEYTAB_PATH','GSSAPI_PRINCIPAL_NAME')
{
print "$var=$ENV{$var}\n";
}
sub is_default { 1 }
bless { };
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/**
@file
GSSAPI authentication plugin, server side
*/
#include <my_sys.h>
#include <mysqld_error.h>
#include <mysql/plugin_auth.h>
#include "server_plugin.h"
#include "common.h"
/* First packet sent from server to client, contains srv_principal_name\0mech\0 */
static char first_packet[PRINCIPAL_NAME_MAX + MECH_NAME_MAX +2];
static int first_packet_len;
/*
Target name in GSSAPI/SSPI , for Kerberos it is service principal name
(often user principal name of the server user will work)
*/
char *srv_principal_name;
char *srv_keytab_path;
char *srv_mech_name=(char *)"";
unsigned long srv_mech;
/**
The main server function of the GSSAPI plugin.
*/
static int gssapi_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *auth_info)
{
int use_full_name;
const char *user;
int user_len;
/* Send first packet with target name and mech name */
if (vio->write_packet(vio, (unsigned char *)first_packet, first_packet_len))
{
return CR_ERROR;
}
/* Figure out whether to use full name (as given in IDENTIFIED AS clause)
* or just short username auth_string
*/
if (auth_info->auth_string_length > 0)
{
use_full_name= 1;
user= auth_info->auth_string;
user_len= auth_info->auth_string_length;
}
else
{
use_full_name= 0;
user= auth_info->user_name;
user_len= auth_info->user_name_length;
}
return auth_server(vio, user, user_len, use_full_name);
}
static int initialize_plugin(void *unused)
{
int rc;
rc = plugin_init();
if (rc)
return rc;
strcpy(first_packet, srv_principal_name);
strcpy(first_packet + strlen(srv_principal_name) + 1,srv_mech_name);
first_packet_len = strlen(srv_principal_name) + strlen(srv_mech_name) + 2;
return 0;
}
static int deinitialize_plugin(void *unused)
{
return plugin_deinit();
}
/* system variable */
static MYSQL_SYSVAR_STR(keytab_path, srv_keytab_path,
PLUGIN_VAR_RQCMDARG|PLUGIN_VAR_READONLY,
"Keytab file path (Kerberos)",
NULL,
NULL,
"");
static MYSQL_SYSVAR_STR(principal_name, srv_principal_name,
PLUGIN_VAR_RQCMDARG|PLUGIN_VAR_READONLY,
"GSSAPI target name - service principal name for Kerberos authentication.",
NULL,
NULL,
"");
#ifdef PLUGIN_SSPI
static const char* mech_names[] = {
"Kerberos",
"Negotiate",
"",
NULL
};
static TYPELIB mech_name_typelib = {
array_elements(mech_names) - 1,
"mech_name_typelib",
mech_names,
NULL
};
static MYSQL_SYSVAR_ENUM(mech_name, srv_mech,
PLUGIN_VAR_RQCMDARG|PLUGIN_VAR_READONLY,
"GSSAPI mechanism : either Kerberos or Negotiate",
NULL,
NULL,
2,&mech_name_typelib);
#endif
static struct st_mysql_sys_var *system_variables[]= {
MYSQL_SYSVAR(principal_name),
#ifdef PLUGIN_SSPI
MYSQL_SYSVAR(mech_name),
#endif
#ifdef PLUGIN_GSSAPI
MYSQL_SYSVAR(keytab_path),
#endif
NULL
};
/* Register authentication plugin */
static struct st_mysql_auth server_handler= {
MYSQL_AUTHENTICATION_INTERFACE_VERSION,
"auth_gssapi_client",
gssapi_auth
};
maria_declare_plugin(gssapi_server)
{
MYSQL_AUTHENTICATION_PLUGIN,
&server_handler,
"gssapi",
"Shuang Qiu, Robbie Harwood, Vladislav Vaintroub",
"Plugin for GSSAPI/SSPI based authentication.",
PLUGIN_LICENSE_BSD,
initialize_plugin,
deinitialize_plugin, /* destructor */
0x0100, /* version */
NULL, /* status variables */
system_variables, /* system variables */
"1.0",
MariaDB_PLUGIN_MATURITY_EXPERIMENTAL
}
maria_declare_plugin_end;
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/* Plugin variables*/
#include <mysql/plugin_auth.h>
typedef enum
{
PLUGIN_MECH_KERBEROS = 0,
PLUGIN_MECH_SPNEGO = 1,
PLUGIN_MECH_DEFAULT = 2
}PLUGIN_MECH;
extern unsigned long srv_mech;
extern char *srv_principal_name;
extern char *srv_mech_name;
extern char *srv_keytab_path;
/*
Check, with GSSAPI/SSPI username of logged on user.
Depending on use_full_name parameter, compare either full name
(principal name like user@real), or local name (first component)
*/
int plugin_init();
int plugin_deinit();
int auth_server(MYSQL_PLUGIN_VIO *vio, const char *username, size_t username_len, int use_full_name);
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#define SECURITY_WIN32
#include <windows.h>
#include <sspi.h>
#include <SecExt.h>
#include <stdarg.h>
#include <stdio.h>
#define SSPI_MAX_TOKEN_SIZE 50000
#define SEC_ERROR(err) (err < 0)
extern void sspi_errmsg(int err, char *buf, size_t size);
\ No newline at end of file
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#define SECURITY_WIN32
#include <windows.h>
#include <sspi.h>
#include <SecExt.h>
#include <stdarg.h>
#include <stdio.h>
#include <mysql/plugin_auth.h>
#include <mysql.h>
#include <mysqld_error.h>
#include "sspi.h"
extern void log_client_error(MYSQL *mysql, const char *fmt, ...);
static void log_error(MYSQL *mysql, SECURITY_STATUS err, const char *msg)
{
if (err)
{
char buf[1024];
sspi_errmsg(err, buf, sizeof(buf));
log_client_error(mysql, "SSPI client error 0x%x - %s - %s", err, msg, buf);
}
else
{
log_client_error(mysql, "SSPI client error %s", msg);
}
}
/** Client side authentication*/
int auth_client(char *principal_name, char *mech, MYSQL *mysql, MYSQL_PLUGIN_VIO *vio)
{
int ret;
CredHandle cred;
CtxtHandle ctxt;
ULONG attribs = 0;
TimeStamp lifetime;
SECURITY_STATUS sspi_err;
SecBufferDesc inbuf_desc;
SecBuffer inbuf;
SecBufferDesc outbuf_desc;
SecBuffer outbuf;
PBYTE out = NULL;
ret= CR_ERROR;
SecInvalidateHandle(&ctxt);
SecInvalidateHandle(&cred);
if (!mech || strcmp(mech, "Negotiate") != 0)
{
mech= "Kerberos";
}
sspi_err = AcquireCredentialsHandle(
NULL,
mech,
SECPKG_CRED_OUTBOUND,
NULL,
NULL,
NULL,
NULL,
&cred,
&lifetime);
if (SEC_ERROR(sspi_err))
{
log_error(mysql, sspi_err, "AcquireCredentialsHandle");
return CR_ERROR;
}
out = (PBYTE)malloc(SSPI_MAX_TOKEN_SIZE);
if (!out)
{
log_error(mysql, SEC_E_OK, "memory allocation error");
goto cleanup;
}
/* Prepare buffers */
inbuf_desc.ulVersion = SECBUFFER_VERSION;
inbuf_desc.cBuffers = 1;
inbuf_desc.pBuffers = &inbuf;
inbuf.BufferType = SECBUFFER_TOKEN;
inbuf.cbBuffer = 0;
inbuf.pvBuffer = NULL;
outbuf_desc.ulVersion = SECBUFFER_VERSION;
outbuf_desc.cBuffers = 1;
outbuf_desc.pBuffers = &outbuf;
outbuf.BufferType = SECBUFFER_TOKEN;
outbuf.pvBuffer = out;
do
{
outbuf.cbBuffer= SSPI_MAX_TOKEN_SIZE;
sspi_err= InitializeSecurityContext(
&cred,
SecIsValidHandle(&ctxt) ? &ctxt : NULL,
principal_name,
0,
0,
SECURITY_NATIVE_DREP,
inbuf.cbBuffer ? &inbuf_desc : NULL,
0,
&ctxt,
&outbuf_desc,
&attribs,
&lifetime);
if (SEC_ERROR(sspi_err))
{
log_error(mysql, sspi_err, "InitializeSecurityContext");
goto cleanup;
}
if (sspi_err != SEC_E_OK && sspi_err != SEC_I_CONTINUE_NEEDED)
{
log_error(mysql, sspi_err, "Unexpected response from InitializeSecurityContext");
goto cleanup;
}
if (outbuf.cbBuffer)
{
/* send credential to server */
if (vio->write_packet(vio, (unsigned char *)outbuf.pvBuffer, outbuf.cbBuffer))
{
/* Server error packet contains detailed message. */
ret= CR_OK_HANDSHAKE_COMPLETE;
goto cleanup;
}
}
if (sspi_err == SEC_I_CONTINUE_NEEDED)
{
int len= vio->read_packet(vio, (unsigned char **)&inbuf.pvBuffer);
if (len <= 0)
{
/* Server side error is in the last server packet. */
ret= CR_OK_HANDSHAKE_COMPLETE;
goto cleanup;
}
inbuf.cbBuffer= len;
}
} while (sspi_err == SEC_I_CONTINUE_NEEDED);
ret= CR_OK;
cleanup:
if (SecIsValidHandle(&ctxt))
DeleteSecurityContext(&ctxt);
if (SecIsValidHandle(&cred))
FreeCredentialsHandle(&cred);
free(out);
return ret;
}
\ No newline at end of file
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <windows.h>
#include <stdio.h>
#define ERRSYM(x) {x, #x}
static struct {
int error;
const char *sym;
} error_symbols[] =
{
ERRSYM(SEC_E_OK),
ERRSYM(SEC_E_INSUFFICIENT_MEMORY),
ERRSYM(SEC_E_INVALID_HANDLE),
ERRSYM(SEC_E_UNSUPPORTED_FUNCTION),
ERRSYM(SEC_E_TARGET_UNKNOWN),
ERRSYM(SEC_E_INTERNAL_ERROR),
ERRSYM(SEC_E_SECPKG_NOT_FOUND),
ERRSYM(SEC_E_NOT_OWNER),
ERRSYM(SEC_E_CANNOT_INSTALL),
ERRSYM(SEC_E_INVALID_TOKEN),
ERRSYM(SEC_E_CANNOT_PACK),
ERRSYM(SEC_E_QOP_NOT_SUPPORTED),
ERRSYM(SEC_E_NO_IMPERSONATION),
ERRSYM(SEC_E_LOGON_DENIED),
ERRSYM(SEC_E_UNKNOWN_CREDENTIALS),
ERRSYM(SEC_E_NO_CREDENTIALS),
ERRSYM(SEC_E_MESSAGE_ALTERED),
ERRSYM(SEC_E_OUT_OF_SEQUENCE),
ERRSYM(SEC_E_NO_AUTHENTICATING_AUTHORITY),
ERRSYM(SEC_E_BAD_PKGID),
ERRSYM(SEC_E_CONTEXT_EXPIRED),
ERRSYM(SEC_E_INCOMPLETE_MESSAGE),
ERRSYM(SEC_E_INCOMPLETE_CREDENTIALS),
ERRSYM(SEC_E_BUFFER_TOO_SMALL),
ERRSYM(SEC_E_WRONG_PRINCIPAL),
ERRSYM(SEC_E_TIME_SKEW),
ERRSYM(SEC_E_UNTRUSTED_ROOT),
ERRSYM(SEC_E_ILLEGAL_MESSAGE),
ERRSYM(SEC_E_CERT_UNKNOWN),
ERRSYM(SEC_E_CERT_EXPIRED),
ERRSYM(SEC_E_ENCRYPT_FAILURE),
ERRSYM(SEC_E_DECRYPT_FAILURE),
ERRSYM(SEC_E_ALGORITHM_MISMATCH),
ERRSYM(SEC_E_SECURITY_QOS_FAILED),
ERRSYM(SEC_E_UNFINISHED_CONTEXT_DELETED),
ERRSYM(SEC_E_NO_TGT_REPLY),
ERRSYM(SEC_E_NO_IP_ADDRESSES),
ERRSYM(SEC_E_WRONG_CREDENTIAL_HANDLE),
ERRSYM(SEC_E_CRYPTO_SYSTEM_INVALID),
ERRSYM(SEC_E_MAX_REFERRALS_EXCEEDED),
ERRSYM(SEC_E_MUST_BE_KDC),
ERRSYM(SEC_E_STRONG_CRYPTO_NOT_SUPPORTED),
ERRSYM(SEC_E_TOO_MANY_PRINCIPALS),
ERRSYM(SEC_E_NO_PA_DATA),
ERRSYM(SEC_E_PKINIT_NAME_MISMATCH),
ERRSYM(SEC_E_SMARTCARD_LOGON_REQUIRED),
ERRSYM(SEC_E_SHUTDOWN_IN_PROGRESS),
ERRSYM(SEC_E_KDC_INVALID_REQUEST),
ERRSYM(SEC_E_KDC_UNABLE_TO_REFER),
ERRSYM(SEC_E_KDC_UNKNOWN_ETYPE),
ERRSYM(SEC_E_UNSUPPORTED_PREAUTH),
ERRSYM(SEC_E_DELEGATION_REQUIRED),
ERRSYM(SEC_E_BAD_BINDINGS),
ERRSYM(SEC_E_MULTIPLE_ACCOUNTS),
ERRSYM(SEC_E_NO_KERB_KEY),
ERRSYM(SEC_E_CERT_WRONG_USAGE),
ERRSYM(SEC_E_DOWNGRADE_DETECTED),
ERRSYM(SEC_E_SMARTCARD_CERT_REVOKED),
ERRSYM(SEC_E_ISSUING_CA_UNTRUSTED),
ERRSYM(SEC_E_REVOCATION_OFFLINE_C),
ERRSYM(SEC_E_PKINIT_CLIENT_FAILURE),
ERRSYM(SEC_E_SMARTCARD_CERT_EXPIRED),
ERRSYM(SEC_E_NO_S4U_PROT_SUPPORT),
ERRSYM(SEC_E_CROSSREALM_DELEGATION_FAILURE),
ERRSYM(SEC_E_REVOCATION_OFFLINE_KDC),
ERRSYM(SEC_E_ISSUING_CA_UNTRUSTED_KDC),
ERRSYM(SEC_E_KDC_CERT_EXPIRED),
ERRSYM(SEC_E_KDC_CERT_REVOKED),
ERRSYM(SEC_E_INVALID_PARAMETER),
ERRSYM(SEC_E_DELEGATION_POLICY),
ERRSYM(SEC_E_POLICY_NLTM_ONLY),
ERRSYM(SEC_E_NO_CONTEXT),
ERRSYM(SEC_E_PKU2U_CERT_FAILURE),
ERRSYM(SEC_E_MUTUAL_AUTH_FAILED),
ERRSYM(SEC_E_NO_SPM),
ERRSYM(SEC_E_NOT_SUPPORTED),
{0,0}
};
void sspi_errmsg(int err, char *buf, size_t size)
{
buf[size - 1] = 0;
size_t len;
for (size_t i= 0; error_symbols[i].sym; i++)
{
if (error_symbols[i].error == err)
{
size_t len= strlen(error_symbols[i].sym);
if (len + 2 < size)
{
memcpy(buf, error_symbols[i].sym, len);
buf[len]= ' ';
buf += len + 1;
size-= len + 1;
}
break;
}
}
len = FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
err, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
buf, size, NULL);
if(len > 0)
{
/* Trim trailing \n\r*/
char *p;
for(p= buf + len;p > buf && (*p == '\n' || *p=='\r' || *p == 0);p--)
*p= 0;
}
}
/* Copyright (c) 2015, Shuang Qiu, Robbie Hardwood,
Vladislav Vaintroub & MariaDB Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "sspi.h"
#include "common.h"
#include "server_plugin.h"
#include <mysql/plugin_auth.h>
#include <my_sys.h>
#include <mysqld_error.h>
#include <log.h>
/* This sends the error to the client */
static void log_error(SECURITY_STATUS err, const char *msg)
{
if (err)
{
char buf[1024];
sspi_errmsg(err, buf, sizeof(buf));
my_printf_error(ER_UNKNOWN_ERROR, "SSPI server error 0x%x - %s - %s", MYF(0), msg, buf);
}
else
{
my_printf_error(ER_UNKNOWN_ERROR, "SSPI server error %s", MYF(0), msg);
}
}
static char INVALID_KERBEROS_PRINCIPAL[] = "localhost";
static char *get_default_principal_name()
{
static char default_principal[PRINCIPAL_NAME_MAX +1];
ULONG size= sizeof(default_principal);
if (GetUserNameEx(NameUserPrincipal,default_principal,&size))
return default_principal;
size= sizeof(default_principal);
if (GetUserNameEx(NameServicePrincipal,default_principal,&size))
return default_principal;
char domain[PRINCIPAL_NAME_MAX+1];
char host[PRINCIPAL_NAME_MAX+1];
size= sizeof(domain);
if (GetComputerNameEx(ComputerNameDnsDomain,domain,&size) && size > 0)
{
size= sizeof(host);
if (GetComputerNameEx(ComputerNameDnsHostname,host,&size))
{
_snprintf(default_principal,sizeof(default_principal),"%s$@%s",host, domain);
return default_principal;
}
}
/* Unable to retrieve useful name, return something */
return INVALID_KERBEROS_PRINCIPAL;
}
/* Extract client name from SSPI context */
static int get_client_name_from_context(CtxtHandle *ctxt,
char *name,
size_t name_len,
int use_full_name)
{
SecPkgContext_NativeNames native_names;
SECURITY_STATUS sspi_ret;
char *p;
sspi_ret= QueryContextAttributes(ctxt, SECPKG_ATTR_NATIVE_NAMES, &native_names);
if (sspi_ret == SEC_E_OK)
{
/* Extract user from Kerberos principal name user@realm */
if(!use_full_name)
{
p = strrchr(native_names.sClientName,'@');
if(p)
*p = 0;
}
strncpy(name, native_names.sClientName, name_len);
FreeContextBuffer(&native_names);
return CR_OK;
}
sspi_ret= ImpersonateSecurityContext(ctxt);
if (sspi_ret == SEC_E_OK)
{
ULONG len= name_len;
if (!GetUserNameEx(NameSamCompatible, name, &len))
{
log_error(GetLastError(), "GetUserNameEx");
RevertSecurityContext(ctxt);
return CR_ERROR;
}
RevertSecurityContext(ctxt);
/* Extract user from Windows name realm\user */
if (!use_full_name)
{
p = strrchr(name, '\\');
if (p)
{
p++;
memmove(name, p, name + len + 1 - p);
}
}
return CR_OK;
}
log_error(sspi_ret, "ImpersonateSecurityContext");
return CR_ERROR;
}
int auth_server(MYSQL_PLUGIN_VIO *vio, const char *user, size_t user_len, int compare_full_name)
{
int ret;
SECURITY_STATUS sspi_ret;
ULONG attribs = 0;
TimeStamp lifetime;
CredHandle cred;
CtxtHandle ctxt;
SecBufferDesc inbuf_desc;
SecBuffer inbuf;
SecBufferDesc outbuf_desc;
SecBuffer outbuf;
void* out= NULL;
char client_name[MYSQL_USERNAME_LENGTH + 1];
ret= CR_ERROR;
SecInvalidateHandle(&cred);
SecInvalidateHandle(&ctxt);
out= malloc(SSPI_MAX_TOKEN_SIZE);
if (!out)
{
log_error(SEC_E_OK, "memory allocation failed");
goto cleanup;
}
sspi_ret= AcquireCredentialsHandle(
srv_principal_name,
srv_mech_name,
SECPKG_CRED_INBOUND,
NULL,
NULL,
NULL,
NULL,
&cred,
&lifetime);
if (SEC_ERROR(sspi_ret))
{
log_error(sspi_ret, "AcquireCredentialsHandle failed");
goto cleanup;
}
inbuf.cbBuffer= 0;
inbuf.BufferType= SECBUFFER_TOKEN;
inbuf.pvBuffer= NULL;
inbuf_desc.ulVersion= SECBUFFER_VERSION;
inbuf_desc.cBuffers= 1;
inbuf_desc.pBuffers= &inbuf;
outbuf.BufferType= SECBUFFER_TOKEN;
outbuf.cbBuffer= SSPI_MAX_TOKEN_SIZE;
outbuf.pvBuffer= out;
outbuf_desc.ulVersion= SECBUFFER_VERSION;
outbuf_desc.cBuffers= 1;
outbuf_desc.pBuffers= &outbuf;
do
{
/* Read SSPI blob from client. */
int len= vio->read_packet(vio, (unsigned char **)&inbuf.pvBuffer);
if (len < 0)
{
log_error(SEC_E_OK, "communication error(read)");
goto cleanup;
}
inbuf.cbBuffer= len;
outbuf.cbBuffer= SSPI_MAX_TOKEN_SIZE;
sspi_ret= AcceptSecurityContext(
&cred,
SecIsValidHandle(&ctxt) ? &ctxt : NULL,
&inbuf_desc,
attribs,
SECURITY_NATIVE_DREP,
&ctxt,
&outbuf_desc,
&attribs,
&lifetime);
if (SEC_ERROR(sspi_ret))
{
log_error(sspi_ret, "AcceptSecurityContext");
goto cleanup;
}
if (sspi_ret != SEC_E_OK && sspi_ret != SEC_I_CONTINUE_NEEDED)
{
log_error(sspi_ret, "AcceptSecurityContext unexpected return value");
goto cleanup;
}
if (outbuf.cbBuffer)
{
/* Send generated blob to client. */
if (vio->write_packet(vio, (unsigned char *)outbuf.pvBuffer, outbuf.cbBuffer))
{
log_error(SEC_E_OK, "communicaton error(write)");
goto cleanup;
}
}
} while (sspi_ret == SEC_I_CONTINUE_NEEDED);
/* Authentication done, now extract and compare user name. */
ret= get_client_name_from_context(&ctxt, client_name, MYSQL_USERNAME_LENGTH, compare_full_name);
if (ret != CR_OK)
goto cleanup;
/* Always compare case-insensitive on Windows. */
ret= _stricmp(client_name, user) == 0 ? CR_OK : CR_ERROR;
if (ret != CR_OK)
{
my_printf_error(ER_ACCESS_DENIED_ERROR,
"GSSAPI name mismatch, requested '%s', actual name '%s'",
MYF(0), user, client_name);
}
cleanup:
if (SecIsValidHandle(&ctxt))
DeleteSecurityContext(&ctxt);
if (SecIsValidHandle(&cred))
FreeCredentialsHandle(&cred);
free(out);
return ret;
}
int plugin_init()
{
CredHandle cred;
SECURITY_STATUS ret;
/*
Use negotiate by default, which accepts raw kerberos
and also NTLM.
*/
if (srv_mech == PLUGIN_MECH_DEFAULT)
srv_mech= PLUGIN_MECH_SPNEGO;
if(srv_mech == PLUGIN_MECH_KERBEROS)
srv_mech_name= "Kerberos";
else if(srv_mech == PLUGIN_MECH_SPNEGO )
srv_mech_name= "Negotiate";
if(!srv_principal_name[0])
{
srv_principal_name= get_default_principal_name();
}
sql_print_information("SSPI: using principal name '%s', mech '%s'",
srv_principal_name, srv_mech_name);
ret = AcquireCredentialsHandle(
srv_principal_name,
srv_mech_name,
SECPKG_CRED_INBOUND,
NULL,
NULL,
NULL,
NULL,
&cred,
NULL);
if (SEC_ERROR(ret))
{
log_error(ret, "AcquireCredentialsHandle");
return -1;
}
FreeCredentialsHandle(&cred);
return 0;
}
int plugin_deinit()
{
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment