summaryrefslogtreecommitdiffstats
path: root/plugin/auth_gssapi/sspi_server.cc
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/auth_gssapi/sspi_server.cc')
-rw-r--r--plugin/auth_gssapi/sspi_server.cc330
1 files changed, 330 insertions, 0 deletions
diff --git a/plugin/auth_gssapi/sspi_server.cc b/plugin/auth_gssapi/sspi_server.cc
new file mode 100644
index 00000000..44aa5051
--- /dev/null
+++ b/plugin/auth_gssapi/sspi_server.cc
@@ -0,0 +1,330 @@
+/* Copyright (c) 2015, Shuang Qiu, Robbie Harwood,
+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 <mysqld_error.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", 0, err, msg, buf);
+ }
+ else
+ {
+ my_printf_error(ER_UNKNOWN_ERROR, "SSPI server error %s", 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);
+
+ if (native_names.sClientName)
+ FreeContextBuffer(native_names.sClientName);
+ if (native_names.sServerName)
+ FreeContextBuffer(native_names.sServerName);
+
+ return CR_OK;
+ }
+
+ sspi_ret= ImpersonateSecurityContext(ctxt);
+ if (sspi_ret == SEC_E_OK)
+ {
+ ULONG len= (ULONG)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, MYSQL_SERVER_AUTH_INFO *auth_info)
+{
+ 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];
+ const char *user= 0;
+ int compare_full_name;
+
+ 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,
+ (LPSTR)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;
+ }
+ if (!user)
+ {
+ if (auth_info->auth_string_length > 0)
+ {
+ compare_full_name= 1;
+ user= auth_info->auth_string;
+ }
+ else
+ {
+ compare_full_name= 0;
+ user= auth_info->user_name;
+ }
+ }
+ 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'",
+ 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();
+ }
+ my_printf_error(ER_UNKNOWN_ERROR, "SSPI: using principal name '%s', mech '%s'",
+ ME_ERROR_LOG | ME_NOTE, srv_principal_name, srv_mech_name);
+
+ ret = AcquireCredentialsHandle(
+ srv_principal_name,
+ (LPSTR)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;
+}