summaryrefslogtreecommitdiffstats
path: root/plugin/auth_gssapi/gssapi_server.cc
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/auth_gssapi/gssapi_server.cc')
-rw-r--r--plugin/auth_gssapi/gssapi_server.cc270
1 files changed, 270 insertions, 0 deletions
diff --git a/plugin/auth_gssapi/gssapi_server.cc b/plugin/auth_gssapi/gssapi_server.cc
new file mode 100644
index 00000000..db7bda9a
--- /dev/null
+++ b/plugin/auth_gssapi/gssapi_server.cc
@@ -0,0 +1,270 @@
+#include <my_config.h>
+#include <gssapi/gssapi.h>
+#include <stdio.h>
+#include <mysql/plugin_auth.h>
+#include <mysqld_error.h>
+#include <string.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",
+ 0, major, minor, msg, sysmsg);
+ }
+ else
+ {
+ my_printf_error(ER_UNKNOWN_ERROR, "Server GSSAPI error : %s", 0, msg);
+ }
+}
+
+
+/*
+ Generate default principal service name formatted as principal name "mariadb/server.fqdn@REALM"
+*/
+#include <krb5.h>
+#ifdef HAVE_KRB5_XFREE
+#define krb5_free_unparsed_name(a,b) krb5_xfree(b)
+#endif
+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))
+ {
+ my_printf_error(1, "GSSAPI plugin : krb5_init_context failed",
+ ME_ERROR_LOG | ME_WARNING);
+ goto cleanup;
+ }
+
+ if (krb5_sname_to_principal(context, NULL, "mariadb", KRB5_NT_SRV_HST, &principal))
+ {
+ my_printf_error(1, "GSSAPI plugin : krb5_sname_to_principal failed",
+ ME_ERROR_LOG | ME_WARNING);
+ goto cleanup;
+ }
+
+ if (krb5_unparse_name(context, principal, &unparsed_name))
+ {
+ my_printf_error(1, "GSSAPI plugin : krb5_unparse_name failed",
+ ME_ERROR_LOG | ME_WARNING);
+ goto cleanup;
+ }
+
+ /* Check for entry in keytab */
+ if (krb5_kt_read_service_key(context, NULL, principal, 0, (krb5_enctype)0, &key))
+ {
+ my_printf_error(1, "GSSAPI plugin : default principal '%s' not found in keytab",
+ ME_ERROR_LOG | ME_WARNING, 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])
+ {
+ my_printf_error(1, "GSSAPI plugin : using principal name '%s'",
+ ME_ERROR_LOG | ME_NOTE, 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, MYSQL_SERVER_AUTH_INFO *auth_info)
+{
+
+ 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;
+ const char *user= 0;
+ size_t userlen= 0;
+ int use_full_name= 0;
+
+ /* 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;
+ }
+ if (!user)
+ {
+ if (auth_info->auth_string_length > 0)
+ {
+ use_full_name= 1;
+ user= auth_info->auth_string;
+ userlen= auth_info->auth_string_length;
+ }
+ else
+ {
+ use_full_name= 0;
+ user= auth_info->user_name;
+ userlen= auth_info->user_name_length;
+ }
+ }
+
+ 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 unsigned char *) 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 (user && 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'",
+ 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);
+}