diff options
Diffstat (limited to '')
-rw-r--r-- | plugin/auth_gssapi/gssapi_server.cc | 270 |
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); +} |