diff options
Diffstat (limited to 'src/responder/pam/pamsrv_gssapi.c')
-rw-r--r-- | src/responder/pam/pamsrv_gssapi.c | 1043 |
1 files changed, 1043 insertions, 0 deletions
diff --git a/src/responder/pam/pamsrv_gssapi.c b/src/responder/pam/pamsrv_gssapi.c new file mode 100644 index 0000000..e4da4c4 --- /dev/null +++ b/src/responder/pam/pamsrv_gssapi.c @@ -0,0 +1,1043 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2020 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <errno.h> +#include <gssapi.h> +#include <gssapi/gssapi_ext.h> +#include <gssapi/gssapi_krb5.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> +#include <ldb.h> + +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "sss_client/sss_cli.h" +#include "util/util.h" +#include "util/sss_utf8.h" + +static errno_t read_str(size_t body_len, + uint8_t *body, + size_t *pctr, + const char **_str) +{ + size_t i; + + for (i = *pctr; i < body_len && body[i] != 0; i++) { + /* counting */ + } + + if (i >= body_len) { + return EINVAL; + } + + if (!sss_utf8_check(&body[*pctr], i - *pctr)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not UTF-8 string!\n"); + return EINVAL; + } + + *_str = (const char *)&body[*pctr]; + *pctr = i + 1; + + return EOK; +} + +static bool pam_gssapi_should_check_upn(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain) +{ + if (domain->gssapi_check_upn != NULL) { + if (strcasecmp(domain->gssapi_check_upn, "true") == 0) { + return true; + } + + if (strcasecmp(domain->gssapi_check_upn, "false") == 0) { + return false; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: %s\n", + CONFDB_PAM_GSSAPI_CHECK_UPN, domain->gssapi_check_upn); + return false; + } + + return pam_ctx->gssapi_check_upn; +} + +static int pam_gssapi_check_indicators(TALLOC_CTX *mem_ctx, + const char *pam_service, + char **gssapi_indicators_map, + char **indicators) +{ + char *authind = NULL; + size_t pam_len = strlen(pam_service); + char **map = gssapi_indicators_map; + char **result = NULL; + int res; + + authind = talloc_strdup(mem_ctx, ""); + if (authind == NULL) { + return ENOMEM; + } + + for (int i = 0; map[i]; i++) { + if (map[i][0] == '-') { + DEBUG(SSSDBG_TRACE_FUNC, + "Indicators aren't used for [%s]\n", + pam_service); + talloc_free(authind); + return EOK; + } + if (!strchr(map[i], ':')) { + authind = talloc_asprintf_append(authind, "%s ", map[i]); + if (authind == NULL) { + /* Since we allocate on pam_ctx, caller will free it */ + return ENOMEM; + } + continue; + } + + res = strncmp(map[i], pam_service, pam_len); + if (res == 0) { + if (strlen(map[i]) > pam_len) { + if (map[i][pam_len] != ':') { + /* different PAM service, skip it */ + continue; + } + + if (map[i][pam_len + 1] == '-') { + DEBUG(SSSDBG_TRACE_FUNC, + "Indicators aren't used for [%s]\n", + pam_service); + talloc_free(authind); + return EOK; + } + + authind = talloc_asprintf_append(authind, "%s ", + map[i] + (pam_len + 1)); + if (authind == NULL) { + /* Since we allocate on pam_ctx, caller will free it */ + return ENOMEM; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: [%s]\n", + CONFDB_PAM_GSSAPI_INDICATORS_MAP, map[i]); + talloc_free(authind); + return EINVAL; + } + } + } + + res = ENOENT; + map = NULL; + + if (authind[0] == '\0') { + /* empty list of per-service indicators -> skip */ + goto done; + } + + /* trim a space after the final indicator + * to prevent split_on_separator() to fail */ + authind[strlen(authind) - 1] = '\0'; + + res = split_on_separator(mem_ctx, authind, ' ', true, true, + &map, NULL); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse list of indicators: [%s]\n", authind); + res = EINVAL; + goto done; + } + + res = diff_string_lists(mem_ctx, indicators, map, NULL, NULL, &result); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE,"Cannot diff lists of indicators\n"); + res = EINVAL; + goto done; + } + + if (result && result[0] != NULL) { + for (int i = 0; result[i]; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "indicator [%s] is allowed for PAM service [%s]\n", + result[i], pam_service); + } + res = EOK; + goto done; + } + + res = EPERM; + +done: + talloc_free(result); + talloc_free(authind); + talloc_free(map); + return res; +} + +static bool pam_gssapi_allowed(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain, + const char *service) +{ + char **list = pam_ctx->gssapi_services; + + if (domain->gssapi_services != NULL) { + list = domain->gssapi_services; + } + + if (strcmp(service, "-") == 0) { + /* Dash is used as a "not set" value to allow to explicitly disable + * gssapi auth for specific domain. Disallow this service to be safe. + */ + DEBUG(SSSDBG_TRACE_FUNC, "Dash - was used as a PAM service name. " + "GSSAPI authentication is not allowed.\n"); + return false; + } + + return string_in_list(service, list, true); +} + +static char *pam_gssapi_target(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "host@%s", domain->hostname); +} + +static const char *pam_gssapi_get_upn(struct cache_req_result *result) +{ + if (result->count == 0) { + return NULL; + } + + /* Canonical UPN should be available if the user has kinited through SSSD. + * Use it as a hint for GSSAPI. Default to empty string so it may be + * more easily transffered over the wire. */ + return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_CANONICAL_UPN, ""); +} + +static const char *pam_gssapi_get_name(struct cache_req_result *result) +{ + if (result->count == 0) { + return NULL; + } + + /* Return username known to SSSD to make sure we authenticated as the same + * user after GSSAPI handshake. */ + return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); +} + +static errno_t pam_gssapi_init_parse(struct cli_protocol *pctx, + const char **_service, + const char **_username) +{ + size_t body_len; + size_t pctr = 0; + uint8_t *body; + errno_t ret; + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + if (body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n"); + return EINVAL; + } + + ret = read_str(body_len, body, &pctr, _service); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _username); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t pam_gssapi_init_reply(struct cli_protocol *pctx, + const char *domain, + const char *target, + const char *upn, + const char *username) +{ + size_t reply_len; + size_t body_len; + size_t pctr; + uint8_t *body; + errno_t ret; + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n", + ret, sss_strerror(ret)); + return ret; + } + + reply_len = strlen(username) + 1; + reply_len += strlen(domain) + 1; + reply_len += strlen(target) + 1; + reply_len += strlen(upn) + 1; + + ret = sss_packet_grow(pctx->creq->out, reply_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create response: %s\n", + sss_strerror(ret)); + return ret; + } + + sss_packet_get_body(pctx->creq->out, &body, &body_len); + + pctr = 0; + SAFEALIGN_SETMEM_STRING(&body[pctr], username, strlen(username) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], domain, strlen(domain) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], target, strlen(target) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], upn, strlen(upn) + 1, &pctr); + + return EOK; +} + +struct gssapi_init_state { + struct cli_ctx *cli_ctx; + const char *username; + const char *service; +}; + +static void pam_cmd_gssapi_init_done(struct tevent_req *req); + +int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx) +{ + struct gssapi_init_state *state; + struct cli_protocol *pctx; + struct tevent_req *req; + const char *username; + const char *service; + const char *attrs[] = { SYSDB_NAME, SYSDB_CANONICAL_UPN, NULL }; + errno_t ret; + + state = talloc_zero(cli_ctx, struct gssapi_init_state); + if (state == NULL) { + return ENOMEM; + } + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + ret = pam_gssapi_init_parse(pctx, &service, &username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse input [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->cli_ctx = cli_ctx; + state->service = service; + state->username = username; + + DEBUG(SSSDBG_TRACE_ALL, + "Requesting GSSAPI authentication of [%s] in service [%s]\n", + username, service); + + req = cache_req_user_by_name_attrs_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + NULL, username, attrs); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_cmd_gssapi_init_done, state); + + ret = EOK; + +done: + if (ret != EOK) { + sss_cmd_send_error(cli_ctx, ret); + sss_cmd_done(cli_ctx, NULL); + } + + return EOK; +} + +static void pam_cmd_gssapi_init_done(struct tevent_req *req) +{ + struct gssapi_init_state *state; + struct cache_req_result *result; + struct cli_protocol *pctx; + struct pam_ctx *pam_ctx; + const char *username; + const char *upn; + char *target; + errno_t ret; + + state = tevent_req_callback_data(req, struct gssapi_init_state); + pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol); + pam_ctx = talloc_get_type(state->cli_ctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_user_by_name_attrs_recv(state, req, &result); + talloc_zfree(req); + if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) { + ret = ENOENT; + goto done; + } else if (ret != EOK) { + goto done; + } + + if (!pam_gssapi_allowed(pam_ctx, result->domain, state->service)) { + ret = ENOTSUP; + goto done; + } + + username = pam_gssapi_get_name(result); + if (username == NULL) { + /* User with no name? */ + ret = ERR_INTERNAL; + goto done; + } + + upn = pam_gssapi_get_upn(result); + if (upn == NULL) { + /* UPN hint may be an empty string, but not NULL. */ + ret = ERR_INTERNAL; + goto done; + } + + target = pam_gssapi_target(state, result->domain); + if (target == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Trying GSSAPI auth: User[%s], Domain[%s], UPN[%s], Target[%s]\n", + username, result->domain->name, upn, target); + + ret = pam_gssapi_init_reply(pctx, result->domain->name, target, upn, + username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to construct reply [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(state->cli_ctx, ret); + } + + sss_cmd_done(state->cli_ctx, state); +} + +static void gssapi_log_status(int type, OM_uint32 status_code) +{ + OM_uint32 message_context = 0; + gss_buffer_desc buf; + OM_uint32 minor; + + do { + gss_display_status(&minor, status_code, type, GSS_C_NO_OID, + &message_context, &buf); + DEBUG(SSSDBG_OP_FAILURE, "GSSAPI: %.*s\n", (int)buf.length, + (char *)buf.value); + gss_release_buffer(&minor, &buf); + } while (message_context != 0); +} + +static void gssapi_log_error(OM_uint32 major, OM_uint32 minor) +{ + gssapi_log_status(GSS_C_GSS_CODE, major); + gssapi_log_status(GSS_C_MECH_CODE, minor); +} + +static char *gssapi_get_name(TALLOC_CTX *mem_ctx, gss_name_t gss_name) +{ + gss_buffer_desc buf; + OM_uint32 major; + OM_uint32 minor; + char *exported; + + major = gss_display_name(&minor, gss_name, &buf, NULL); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export name\n"); + return NULL; + } + + exported = talloc_strndup(mem_ctx, buf.value, buf.length); + gss_release_buffer(&minor, &buf); + + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return NULL; + } + + return exported; +} + +#define AUTH_INDICATORS_TAG "auth-indicators" + +static char **gssapi_get_indicators(TALLOC_CTX *mem_ctx, gss_name_t gss_name) +{ + gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET; + int is_mechname; + OM_uint32 major; + OM_uint32 minor; + gss_buffer_desc value = GSS_C_EMPTY_BUFFER; + gss_buffer_desc display_value = GSS_C_EMPTY_BUFFER; + char *exported = NULL; + char **map = NULL; + int res; + + major = gss_inquire_name(&minor, gss_name, &is_mechname, NULL, &attrs); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to inquire name\n"); + return NULL; + } + + if (attrs == GSS_C_NO_BUFFER_SET) { + DEBUG(SSSDBG_TRACE_FUNC, "No krb5 attributes in the ticket\n"); + return NULL; + } + + exported = talloc_strdup(mem_ctx, ""); + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to pre-allocate indicators\n"); + goto done; + } + + for (int i = 0; i < attrs->count; i++) { + int authenticated = 0; + int complete = 0; + int more = -1; + + /* skip anything but auth-indicators */ + if (strncmp(AUTH_INDICATORS_TAG, attrs->elements[i].value, + sizeof(AUTH_INDICATORS_TAG) - 1) != 0) + continue; + + /* retrieve all indicators */ + while (more != 0) { + value.value = NULL; + display_value.value = NULL; + + major = gss_get_name_attribute(&minor, gss_name, + &attrs->elements[i], + &authenticated, + &complete, &value, + &display_value, + &more); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to retrieve an attribute\n"); + goto done; + } + + if ((value.value != NULL) && authenticated) { + DEBUG(SSSDBG_TRACE_FUNC, + "attribute's [%.*s] value [%.*s] authenticated\n", + (int) attrs->elements[i].length, + (char*) attrs->elements[i].value, + (int) value.length, + (char*) value.value); + exported = talloc_asprintf_append(exported, "%.*s ", + (int) value.length, + (char*) value.value); + } + + if (exported == NULL) { + /* Since we allocate on mem_ctx, caller will free + * the previous version of 'exported' */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to collect an attribute value\n"); + goto done; + } + (void) gss_release_buffer(&minor, &value); + (void) gss_release_buffer(&minor, &display_value); + } + } + + if (exported[0] != '\0') { + /* trim a space after the final indicator + * to prevent split_on_separator() to fail */ + exported[strlen(exported) - 1] = '\0'; + } else { + /* empty list */ + goto done; + } + + res = split_on_separator(mem_ctx, exported, ' ', true, true, + &map, NULL); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse list of indicators: [%s]\n", exported); + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "authentication indicators: [%s]\n", + exported); + } + +done: + (void) gss_release_buffer(&minor, &value); + (void) gss_release_buffer(&minor, &display_value); + (void) gss_release_buffer_set(&minor, &attrs); + + talloc_free(exported); + return map; +} + + +struct gssapi_state { + struct cli_ctx *cli_ctx; + struct sss_domain_info *domain; + const char *username; + + char *authenticated_upn; + char **auth_indicators; + bool established; + gss_ctx_id_t ctx; +}; + +int gssapi_state_destructor(struct gssapi_state *state) +{ + OM_uint32 minor; + + gss_delete_sec_context(&minor, &state->ctx, NULL); + + return 0; +} + +static struct gssapi_state *gssapi_get_state(struct cli_ctx *cli_ctx, + const char *username, + struct sss_domain_info *domain) +{ + struct gssapi_state *state; + + state = talloc_get_type(cli_ctx->state_ctx, struct gssapi_state); + if (state != NULL) { + return state; + } + + state = talloc_zero(cli_ctx, struct gssapi_state); + if (state == NULL) { + return NULL; + } + + state->username = talloc_strdup(state, username); + if (state == NULL) { + talloc_free(state); + return NULL; + } + + state->domain = domain; + state->cli_ctx = cli_ctx; + state->ctx = GSS_C_NO_CONTEXT; + talloc_set_destructor(state, gssapi_state_destructor); + + cli_ctx->state_ctx = state; + + return state; +} + +static errno_t gssapi_get_creds(const char *keytab, + const char *target, + gss_cred_id_t *_creds) +{ + gss_key_value_set_desc cstore = {0, NULL}; + gss_key_value_element_desc el; + gss_buffer_desc name_buf; + gss_name_t name = GSS_C_NO_NAME; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + if (keytab != NULL) { + el.key = "keytab"; + el.value = keytab; + cstore.count = 1; + cstore.elements = ⪙ + } + + if (target != NULL) { + name_buf.value = discard_const(target); + name_buf.length = strlen(target); + + major = gss_import_name(&minor, &name_buf, GSS_C_NT_HOSTBASED_SERVICE, + &name); + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Could not import name [%s] " + "[maj:0x%x, min:0x%x]\n", target, major, minor); + + gssapi_log_error(major, minor); + + ret = EIO; + goto done; + } + } + + major = gss_acquire_cred_from(&minor, name, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cstore, + _creds, NULL, NULL); + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to read credentials from [%s] " + "[maj:0x%x, min:0x%x]\n", keytab ? keytab : "default", + major, minor); + + gssapi_log_error(major, minor); + + ret = EIO; + goto done; + } + + ret = EOK; + +done: + gss_release_name(&minor, &name); + + return ret; +} + +static errno_t +gssapi_handshake(struct gssapi_state *state, + struct cli_protocol *pctx, + const char *keytab, + const char *target, + uint8_t *gss_data, + size_t gss_data_len) +{ + OM_uint32 flags = GSS_C_MUTUAL_FLAG; + gss_buffer_desc output = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input; + gss_name_t client_name; + gss_cred_id_t creds; + OM_uint32 ret_flags; + gss_OID mech_type; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + input.value = gss_data; + input.length = gss_data_len; + + ret = gssapi_get_creds(keytab, target, &creds); + if (ret != EOK) { + return ret; + } + + major = gss_accept_sec_context(&minor, &state->ctx, creds, + &input, NULL, &client_name, &mech_type, + &output, &ret_flags, NULL, NULL); + if (major == GSS_S_CONTINUE_NEEDED || output.length > 0) { + ret = sss_packet_set_body(pctx->creq->out, output.value, output.length); + if (ret != EOK) { + goto done; + } + } + + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context " + "[maj:0x%x, min:0x%x]\n", major, minor); + + gssapi_log_error(major, minor); + ret = EIO; + goto done; + } + + if (major == GSS_S_CONTINUE_NEEDED) { + ret = EOK; + goto done; + } else if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context, unexpected " + "value: 0x%x\n", major); + ret = EIO; + goto done; + } + + if ((ret_flags & flags) != flags) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Negotiated context does not support requested flags\n"); + state->established = false; + ret = EIO; + goto done; + } + + state->authenticated_upn = gssapi_get_name(state, client_name); + if (state->authenticated_upn == NULL) { + state->established = false; + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Security context established with [%s]\n", + state->authenticated_upn); + + state->auth_indicators = gssapi_get_indicators(state, client_name); + + state->established = true; + ret = EOK; + +done: + gss_release_cred(&minor, &creds); + gss_release_buffer(&minor, &output); + + return ret; +} + +static errno_t pam_cmd_gssapi_sec_ctx_parse(struct cli_protocol *pctx, + const char **_pam_service, + const char **_username, + const char **_domain, + uint8_t **_gss_data, + size_t *_gss_data_len) +{ + size_t body_len; + uint8_t *body; + size_t pctr; + errno_t ret; + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + if (body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n"); + return EINVAL; + } + + pctr = 0; + ret = read_str(body_len, body, &pctr, _pam_service); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _username); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _domain); + if (ret != EOK) { + return ret; + } + + *_gss_data = (pctr == body_len) ? NULL : body + pctr; + *_gss_data_len = body_len - pctr; + + return EOK; +} + +static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req); + +int +pam_cmd_gssapi_sec_ctx(struct cli_ctx *cli_ctx) +{ + struct sss_domain_info *domain; + struct gssapi_state *state; + struct cli_protocol *pctx; + struct pam_ctx *pam_ctx; + struct tevent_req *req; + const char *pam_service; + const char *domain_name; + const char *username; + char *target; + char **indicators_map = NULL; + size_t gss_data_len; + uint8_t *gss_data; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + pam_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = pam_cmd_gssapi_sec_ctx_parse(pctx, &pam_service, &username, + &domain_name, &gss_data, &gss_data_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse input data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + domain = find_domain_by_name(cli_ctx->rctx->domains, domain_name, false); + if (domain == NULL) { + ret = EINVAL; + goto done; + } + + if (!pam_gssapi_allowed(pam_ctx, domain, pam_service)) { + ret = ENOTSUP; + goto done; + } + + target = pam_gssapi_target(cli_ctx, domain); + if (target == NULL) { + ret = ENOMEM; + goto done; + } + + state = gssapi_get_state(cli_ctx, username, domain); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + if (strcmp(username, state->username) != 0 || state->domain != domain) { + /* This should not happen, but be paranoid. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Different input user then who initiated " + "the request!\n"); + ret = EPERM; + goto done; + } + + if (state->established) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Security context is already established\n"); + ret = EPERM; + goto done; + } + + ret = gssapi_handshake(state, pctx, domain->krb5_keytab, target, gss_data, + gss_data_len); + if (ret != EOK || !state->established) { + goto done; + } + + /* Use map for auth-indicators from the domain, if defined and + * fallback to the [pam] section otherwise */ + indicators_map = domain->gssapi_indicators_map ? + domain->gssapi_indicators_map : + (pam_ctx->gssapi_indicators_map ? + pam_ctx->gssapi_indicators_map : NULL); + if (indicators_map != NULL) { + ret = pam_gssapi_check_indicators(state, + pam_service, + indicators_map, + state->auth_indicators); + DEBUG(SSSDBG_TRACE_FUNC, + "Check if acquired service ticket has req. indicators: %d\n", + ret); + if ((ret == EPERM) || (ret == ENOMEM) || (ret == EINVAL)) { + /* skip further checks if denied or no memory, + * ENOENT means the check is not applicable */ + goto done; + } + } + + if (!pam_gssapi_should_check_upn(pam_ctx, domain)) { + /* We are done. */ + goto done; + } + + /* We have established the security context. Now check the the principal + * used for authorization can be associated with the user. We have + * already done initgroups before so we could just search the sysdb + * directly, but use cache req to avoid looking up a possible expired + * object if the handshake took longer. */ + + DEBUG(SSSDBG_TRACE_FUNC, "Checking that target user matches UPN\n"); + + req = cache_req_user_by_upn_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + CACHE_REQ_POSIX_DOM, + domain->name, state->authenticated_upn); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_cmd_gssapi_sec_ctx_done, state); + + return EOK; + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(cli_ctx, ret); + } + + sss_cmd_done(cli_ctx, NULL); + return EOK; +} + +static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req) +{ + struct gssapi_state *state; + struct cache_req_result *result; + struct cli_protocol *pctx; + const char *name; + errno_t ret; + + state = tevent_req_callback_data(req, struct gssapi_state); + pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol); + + ret = cache_req_user_by_upn_recv(state, req, &result); + talloc_zfree(req); + if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) { + /* We have no match. Return failure. */ + DEBUG(SSSDBG_TRACE_FUNC, "User with UPN [%s] was not found. " + "Authentication failed.\n", state->authenticated_upn); + ret = EACCES; + goto done; + } else if (ret != EOK) { + /* Generic error. Return failure. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user by UPN [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Check that username match. */ + name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); + if (name == NULL || strcmp(name, state->username) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "UPN [%s] does not match target user [%s]. " + "Authentication failed.\n", state->authenticated_upn, + state->username); + ret = EACCES; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "User [%s] match UPN [%s]. Authentication was " + "successful.\n", state->username, state->authenticated_upn); + + ret = EOK; + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(state->cli_ctx, ret); + } + + sss_cmd_done(state->cli_ctx, state); +} |