diff options
Diffstat (limited to 'src/krb5_plugin')
-rw-r--r-- | src/krb5_plugin/common/radius_clpreauth.c | 46 | ||||
-rw-r--r-- | src/krb5_plugin/common/radius_clpreauth.h | 37 | ||||
-rw-r--r-- | src/krb5_plugin/common/radius_kdcpreauth.c | 611 | ||||
-rw-r--r-- | src/krb5_plugin/common/radius_kdcpreauth.h | 185 | ||||
-rw-r--r-- | src/krb5_plugin/common/utils.c | 254 | ||||
-rw-r--r-- | src/krb5_plugin/common/utils.h | 68 | ||||
-rw-r--r-- | src/krb5_plugin/idp/idp.h | 70 | ||||
-rw-r--r-- | src/krb5_plugin/idp/idp_clpreauth.c | 228 | ||||
-rw-r--r-- | src/krb5_plugin/idp/idp_kdcpreauth.c | 406 | ||||
-rw-r--r-- | src/krb5_plugin/idp/idp_utils.c | 273 | ||||
-rw-r--r-- | src/krb5_plugin/idp/sssd_enable_idp | 14 | ||||
-rw-r--r-- | src/krb5_plugin/passkey/passkey.h | 110 | ||||
-rw-r--r-- | src/krb5_plugin/passkey/passkey_clpreauth.c | 395 | ||||
-rw-r--r-- | src/krb5_plugin/passkey/passkey_kdcpreauth.c | 438 | ||||
-rw-r--r-- | src/krb5_plugin/passkey/passkey_utils.c | 648 | ||||
-rw-r--r-- | src/krb5_plugin/passkey/sssd_enable_passkey | 14 | ||||
-rw-r--r-- | src/krb5_plugin/sssd_krb5_localauth_plugin.c | 196 | ||||
-rw-r--r-- | src/krb5_plugin/sssd_krb5_locator_plugin.c | 646 |
18 files changed, 4639 insertions, 0 deletions
diff --git a/src/krb5_plugin/common/radius_clpreauth.c b/src/krb5_plugin/common/radius_clpreauth.c new file mode 100644 index 0000000..b9779b2 --- /dev/null +++ b/src/krb5_plugin/common/radius_clpreauth.c @@ -0,0 +1,46 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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 <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <krad.h> +#include <krb5/kdcpreauth_plugin.h> + +#include "radius_kdcpreauth.h" +#include "util/util.h" + +void +sss_radiuscl_init(krb5_context context, + krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq *modreq_out) +{ + return; +} + +void +sss_radiuscl_fini(krb5_context context, + krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq modreq) +{ + return; +} diff --git a/src/krb5_plugin/common/radius_clpreauth.h b/src/krb5_plugin/common/radius_clpreauth.h new file mode 100644 index 0000000..1dfe3df --- /dev/null +++ b/src/krb5_plugin/common/radius_clpreauth.h @@ -0,0 +1,37 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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/>. +*/ + +#ifndef _RADIUS_CLPREAUTH_H_ +#define _RADIUS_CLPREAUTH_H_ + +#include <stdlib.h> +#include <krb5/preauth_plugin.h> + +void +sss_radiuscl_init(krb5_context context, + krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq *modreq_out); + +void +sss_radiuscl_fini(krb5_context context, + krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq modreq); + +#endif /* _RADIUS_CLPREAUTH_H_ */ diff --git a/src/krb5_plugin/common/radius_kdcpreauth.c b/src/krb5_plugin/common/radius_kdcpreauth.c new file mode 100644 index 0000000..77f3661 --- /dev/null +++ b/src/krb5_plugin/common/radius_kdcpreauth.c @@ -0,0 +1,611 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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 <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <krad.h> +#include <krb5/kdcpreauth_plugin.h> + +#include "krb5_plugin/common/radius_kdcpreauth.h" +#include "krb5_plugin/common/utils.h" +#include "util/util.h" + +krb5_error_code +sss_radiuskdc_init(const char *plugin_name, + krb5_context kctx, + krb5_kdcpreauth_moddata *_moddata, + const char **_realmnames) +{ + struct sss_radiuskdc_state *state; + + state = malloc(sizeof(struct sss_radiuskdc_state)); + if (state == NULL) { + return ENOMEM; + } + + state->plugin_name = plugin_name; + + /* IPA is the only consumer so far so it is fine to hardcode the values. */ + state->server = KRB5_KDC_RUNDIR "/DEFAULT.socket"; + state->secret = ""; + state->timeout = 5 * 1000; + state->retries = 3; + + *_moddata = (krb5_kdcpreauth_moddata)state; + + return 0; +} + +void +sss_radiuskdc_fini(krb5_context kctx, + krb5_kdcpreauth_moddata moddata) +{ + struct sss_radiuskdc_state *state; + + state = (struct sss_radiuskdc_state *)moddata; + + if (state == NULL) { + return; + } + + free(state); +} + +int +sss_radiuskdc_flags(krb5_context kctx, + krb5_preauthtype pa_type) +{ + return PA_REPLACES_KEY; +} + +krb5_error_code +sss_radiuskdc_return_padata(krb5_context kctx, + krb5_pa_data *padata, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa_out, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, + krb5_kdcpreauth_modreq modreq) +{ + struct sss_radiuskdc_state *state; + krb5_keyblock *armor_key; + bool *result; + + state = (struct sss_radiuskdc_state *)moddata; + result = (bool *)modreq; + + /* This should not happen. */ + if (state == NULL) { + return EINVAL; + } + + /* Verification was not successful. Do not replace the key. */ + if (result == NULL || *result == false) { + return 0; + } + + /* Get the armor key. */ + armor_key = cb->fast_armor(kctx, rock); + if (armor_key == NULL) { + com_err(state->plugin_name, ENOENT, + "No armor key found when returning padata"); + return ENOENT; + } + + /* Replace the reply key with the FAST armor key. */ + krb5_free_keyblock_contents(kctx, encrypting_key); + return krb5_copy_keyblock_contents(kctx, armor_key, encrypting_key); +} + +krb5_error_code +sss_radiuskdc_enabled(const char *config_name, + krb5_context kctx, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + char **_config) +{ + krb5_error_code ret; + char *config; + + ret = cb->get_string(kctx, rock, config_name, &config); + if (ret != 0) { + return ret; + } + + /* Disabled. */ + if (config == NULL) { + return ENOENT; + } + + /* Enabled. Return the config string. */ + *_config = config; + + return 0; +} + +void +sss_radiuskdc_config_free(struct sss_radiuskdc_config *config) +{ + if (config == NULL) { + return; + } + + free(config->username); + free(config->server); + free(config->secret); + free(config); +} + +krb5_error_code +sss_radiuskdc_config_init(struct sss_radiuskdc_state *state, + krb5_context kctx, + krb5_const_principal princ, + const char *configstr, + struct sss_radiuskdc_config **_config) +{ + struct sss_radiuskdc_config *config; + krb5_error_code ret; + char *username; + + if (state == NULL) { + return EINVAL; + } + + config = malloc(sizeof(struct sss_radiuskdc_config)); + if (config == NULL) { + ret = ENOMEM; + goto done; + } + memset(config, 0, sizeof(struct sss_radiuskdc_config)); + + config->server = strdup(state->server); + config->secret = strdup(state->secret); + config->retries = state->retries; + config->timeout = state->timeout; + + if (config->server == NULL || config->secret == NULL) { + ret = ENOMEM; + goto done; + } + + ret = krb5_unparse_name_flags(kctx, princ, 0, &username); + if (ret != 0) { + goto done; + } + + config->username = strdup(username); + krb5_free_unparsed_name(kctx, username); + if (config->username == NULL) { + ret = ENOMEM; + goto done; + } + + *_config = config; + ret = 0; + +done: + if (ret != 0) { + sss_radiuskdc_config_free(config); + } + + return ret; +} + +krb5_error_code +sss_radiuskdc_set_cookie(krb5_context context, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_preauthtype pa_type, + const krb5_data *state) +{ + krb5_data cookie; + unsigned int len; + uint8_t *blob; + size_t pctr; + + len = sizeof(uint16_t) + state->length; + blob = malloc(len); + if (blob == NULL) { + return ENOMEM; + } + + pctr = 0; + SAFEALIGN_SET_UINT16(&blob[pctr], 1, &pctr); + SAFEALIGN_SET_STRING(&blob[pctr], state->data, state->length, &pctr); + + cookie.magic = 0; + cookie.data = (char *)blob; + cookie.length = len; + + return cb->set_cookie(context, rock, pa_type, &cookie); +} + +krb5_error_code +sss_radiuskdc_get_cookie(krb5_context context, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_preauthtype pa_type, + krb5_data *_state) +{ + uint16_t version; + krb5_data cookie; + krb5_data state; + size_t pctr; + + if (!cb->get_cookie(context, rock, pa_type, &cookie)) { + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + if (cookie.length < sizeof(uint16_t)) { + return EINVAL; + } + + pctr = 0; + SAFEALIGN_COPY_UINT16(&version, cookie.data, &pctr); + state.magic = 0; + state.data = &cookie.data[pctr]; + state.length = cookie.length - sizeof(uint16_t); + + *_state = state; + + return 0; +} + + +/* Some attributes have limited length. In order to accept longer values, + * we will concatenate all attribute values to single krb5_data. */ +krb5_error_code +sss_radiuskdc_get_complete_attr(const krad_packet *rres, + const char *attr_name, + krb5_data *_data) +{ + krad_attr attr = krad_attr_name2num(attr_name); + const krb5_data *rmsg; + krb5_data data = {0}; + unsigned int memindex; + unsigned int i; + + i = 0; + do { + rmsg = krad_packet_get_attr(rres, attr, i); + if (rmsg != NULL) { + data.length += rmsg->length; + } + i++; + } while (rmsg != NULL); + + if (data.length == 0) { + return ENOENT; + } + + data.data = malloc(data.length); + if (data.data == NULL) { + return ENOMEM; + } + + i = 0; + memindex = 0; + do { + rmsg = krad_packet_get_attr(rres, attr, i); + if (rmsg != NULL) { + memcpy(&data.data[memindex], rmsg->data, rmsg->length); + memindex += rmsg->length; + } + i++; + } while (rmsg != NULL); + + if (memindex != data.length) { + free(data.data); + return ERANGE; + } + + *_data = data; + + return 0; +} + +/* From krad internals, RFC 2865 */ +#ifndef UCHAR_MAX +#define UCHAR_MAX 255 +#endif +#define MAX_ATTRSIZE (UCHAR_MAX - 2) + +krb5_error_code +sss_radiuskdc_put_complete_attr(krad_attrset *attrset, + krad_attr attr, + const krb5_data *datap) +{ + krb5_data state = {0}; + char *p = datap->data; + unsigned int len = datap->length; + krb5_error_code ret = 0; + + do { + /* - 5 to make sure we fit into minimal value length */ + state.data = p; + state.length = MIN(MAX_ATTRSIZE - 5, len); + p += state.length; + + ret = krad_attrset_add(attrset, attr, &(state)); + if (ret != 0) { + break; + } + len -= state.length; + } while (len > 0); + + return ret; +} + +char * +sss_radiuskdc_get_attr_as_string(const krad_packet *packet, const char *attr) +{ + krb5_data data = {0}; + krb5_error_code ret; + char *str; + + ret = sss_radiuskdc_get_complete_attr(packet, attr, &data); + if (ret != 0) { + return NULL; + } + + str = strndup(data.data, data.length); + free(data.data); + + return str; +} + +krb5_error_code +sss_radiuskdc_set_attr_as_string(krad_attrset *attrset, + const char *attr, + const char *value) +{ + krb5_data data = {0}; + krb5_error_code ret; + + data.data = discard_const(value); + data.length = strlen(value) + 1; + + ret = sss_radiuskdc_put_complete_attr(attrset, + krad_attr_name2num(attr), + &data); + + return ret; +} + +void +sss_radiuskdc_client_free(struct sss_radiuskdc_client *client) +{ + if (client == NULL) { + return; + } + + krad_client_free(client->client); + krad_attrset_free(client->attrs); + free(client); +} + +struct sss_radiuskdc_client * +sss_radiuskdc_client_init(krb5_context kctx, + verto_ctx *vctx, + struct sss_radiuskdc_config *config) +{ + struct sss_radiuskdc_client *client; + char hostname[HOST_NAME_MAX + 1]; + krb5_data data = {0}; + krb5_error_code ret; + + client = malloc(sizeof(struct sss_radiuskdc_client)); + if (client == NULL) { + return NULL; + } + memset(client, 0, sizeof(struct sss_radiuskdc_client)); + + ret = krad_client_new(kctx, vctx, &client->client); + if (ret != 0) { + goto fail; + } + + ret = krad_attrset_new(kctx, &client->attrs); + if (ret != 0) { + goto fail; + } + + ret = gethostname(hostname, sizeof(hostname) / sizeof(char)); + if (ret != 0) { + goto fail; + } + + data.data = hostname; + data.length = strlen(hostname); + ret = krad_attrset_add(client->attrs, krad_attr_name2num("NAS-Identifier"), + &data); + if (ret != 0) { + goto fail; + } + + ret = krad_attrset_add_number(client->attrs, krad_attr_name2num("Service-Type"), + KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY); + if (ret != 0) { + goto fail; + } + + data.data = config->username; + data.length = strlen(config->username); + ret = krad_attrset_add(client->attrs, krad_attr_name2num("User-Name"), + &data); + if (ret != 0) { + goto fail; + } + + return client; + +fail: + sss_radiuskdc_client_free(client); + return NULL; +} + +void +sss_radiuskdc_challenge_free(struct sss_radiuskdc_challenge *state) +{ + if (state == NULL) { + return; + } + + sss_radiuskdc_client_free(state->client); + free(state); +} + +struct sss_radiuskdc_challenge * +sss_radiuskdc_challenge_init(krb5_context kctx, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_edata_respond_fn respond, + void *arg, + struct sss_radiuskdc_config *config) +{ + struct sss_radiuskdc_challenge *state; + + state = malloc(sizeof(struct sss_radiuskdc_challenge)); + if (state == NULL) { + return NULL; + } + memset(state, 0, sizeof(struct sss_radiuskdc_challenge)); + + state->kctx = kctx; + state->cb = cb; + state->rock = rock; + state->respond = respond; + state->arg = arg; + + state->client = sss_radiuskdc_client_init(kctx, + cb->event_context(kctx, rock), + config); + if (state->client == NULL) { + sss_radiuskdc_challenge_free(state); + return NULL; + } + + return state; +} + +void +sss_radiuskdc_verify_free(struct sss_radiuskdc_verify *state) +{ + if (state == NULL) { + return; + } + + sss_string_array_free(state->indicators); + sss_radiuskdc_client_free(state->client); + free(state); +} + +struct sss_radiuskdc_verify * +sss_radiuskdc_verify_init(krb5_context kctx, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_callbacks cb, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_kdcpreauth_verify_respond_fn respond, + void *arg, + char **indicators, + struct sss_radiuskdc_config *config) +{ + struct sss_radiuskdc_verify *state; + + state = malloc(sizeof(struct sss_radiuskdc_verify)); + if (state == NULL) { + return NULL; + } + memset(state, 0, sizeof(struct sss_radiuskdc_verify)); + + state->kctx = kctx; + state->rock = rock; + state->cb = cb; + state->enc_tkt_reply = enc_tkt_reply; + state->respond = respond; + state->arg = arg; + + state->indicators = sss_string_array_copy(indicators); + if (state->indicators == NULL) { + sss_radiuskdc_verify_free(state); + return NULL; + } + + state->client = sss_radiuskdc_client_init(kctx, + cb->event_context(kctx, rock), + config); + if (state->client == NULL) { + sss_radiuskdc_verify_free(state); + return NULL; + } + + return state; +} + +void +sss_radiuskdc_verify_done(krb5_error_code rret, + const krad_packet *rreq, + const krad_packet *rres, + void *data) +{ + static bool verify_success = true; + static bool verify_failure = false; + struct sss_radiuskdc_verify *state; + krb5_kdcpreauth_modreq modreq; + krb5_error_code ret; + int i; + + state = (struct sss_radiuskdc_verify *)data; + modreq = (krb5_kdcpreauth_modreq)&verify_failure; + + if (rret != 0) { + ret = rret; + goto done; + } + + if (krad_packet_get_code(rres) != krad_code_name2num("Access-Accept")) { + ret = KRB5_PREAUTH_FAILED; + goto done; + } + + state->enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH; + + for (i = 0; state->indicators[i] != NULL; i++) { + ret = state->cb->add_auth_indicator(state->kctx, state->rock, + state->indicators[i]); + if (ret != 0) { + goto done; + } + } + + modreq = (krb5_kdcpreauth_modreq)&verify_success; + ret = 0; + +done: + state->respond(state->arg, ret, modreq, NULL, NULL); + sss_radiuskdc_verify_free(state); +} diff --git a/src/krb5_plugin/common/radius_kdcpreauth.h b/src/krb5_plugin/common/radius_kdcpreauth.h new file mode 100644 index 0000000..7e032b3 --- /dev/null +++ b/src/krb5_plugin/common/radius_kdcpreauth.h @@ -0,0 +1,185 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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/>. +*/ + +#ifndef _RADIUS_KDCPREAUTH_H_ +#define _RADIUS_KDCPREAUTH_H_ + +#include <stdlib.h> +#include <krb5/preauth_plugin.h> + +struct sss_radiuskdc_state { + const char *plugin_name; + const char *server; + const char *secret; + size_t retries; + int timeout; +}; + +struct sss_radiuskdc_config { + char *username; + char *server; + char *secret; + size_t retries; + int timeout; +}; + +struct sss_radiuskdc_client { + krad_client *client; + krad_attrset *attrs; +}; + +struct sss_radiuskdc_challenge { + struct sss_radiuskdc_client *client; + + krb5_context kctx; + krb5_kdcpreauth_callbacks cb; + krb5_kdcpreauth_rock rock; + krb5_kdcpreauth_edata_respond_fn respond; + void *arg; +}; + +struct sss_radiuskdc_verify { + struct sss_radiuskdc_client *client; + char **indicators; + + krb5_context kctx; + krb5_kdcpreauth_rock rock; + krb5_kdcpreauth_callbacks cb; + krb5_enc_tkt_part *enc_tkt_reply; + krb5_kdcpreauth_verify_respond_fn respond; + void *arg; +}; + +krb5_error_code +sss_radiuskdc_init(const char *plugin_name, + krb5_context kctx, + krb5_kdcpreauth_moddata *_moddata, + const char **_realmnames); + +void +sss_radiuskdc_fini(krb5_context kctx, + krb5_kdcpreauth_moddata moddata); + +int +sss_radiuskdc_flags(krb5_context kctx, + krb5_preauthtype pa_type); + +krb5_error_code +sss_radiuskdc_return_padata(krb5_context kctx, + krb5_pa_data *padata, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa_out, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, + krb5_kdcpreauth_modreq modreq); + +krb5_error_code +sss_radiuskdc_enabled(const char *config_name, + krb5_context kctx, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + char **_config); + +void +sss_radiuskdc_config_free(struct sss_radiuskdc_config *config); + +krb5_error_code +sss_radiuskdc_config_init(struct sss_radiuskdc_state *state, + krb5_context kctx, + krb5_const_principal princ, + const char *configstr, + struct sss_radiuskdc_config **_config); + +krb5_error_code +sss_radiuskdc_set_cookie(krb5_context context, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_preauthtype pa_type, + const krb5_data *state); + +krb5_error_code +sss_radiuskdc_get_cookie(krb5_context context, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_preauthtype pa_type, + krb5_data *_state); + +krb5_error_code +sss_radiuskdc_get_complete_attr(const krad_packet *rres, + const char *attr_name, + krb5_data *_data); + +krb5_error_code +sss_radiuskdc_put_complete_attr(krad_attrset *attrset, + krad_attr attr, + const krb5_data *datap); + +char * +sss_radiuskdc_get_attr_as_string(const krad_packet *packet, const char *attr); + + +krb5_error_code +sss_radiuskdc_set_attr_as_string(krad_attrset *attrset, + const char *attr, + const char *value); + +void +sss_radiuskdc_client_free(struct sss_radiuskdc_client *client); + +struct sss_radiuskdc_client * +sss_radiuskdc_client_init(krb5_context kctx, + verto_ctx *vctx, + struct sss_radiuskdc_config *config); + +void +sss_radiuskdc_challenge_free(struct sss_radiuskdc_challenge *state); + +struct sss_radiuskdc_challenge * +sss_radiuskdc_challenge_init(krb5_context kctx, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_edata_respond_fn respond, + void *arg, + struct sss_radiuskdc_config *config); + +void +sss_radiuskdc_verify_free(struct sss_radiuskdc_verify *state); + +struct sss_radiuskdc_verify * +sss_radiuskdc_verify_init(krb5_context kctx, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_callbacks cb, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_kdcpreauth_verify_respond_fn respond, + void *arg, + char **indicators, + struct sss_radiuskdc_config *config); + +void +sss_radiuskdc_verify_done(krb5_error_code rret, + const krad_packet *rreq, + const krad_packet *rres, + void *data); + +#endif /* _RADIUS_KDCPREAUTH_H_ */ diff --git a/src/krb5_plugin/common/utils.c b/src/krb5_plugin/common/utils.c new file mode 100644 index 0000000..2a0ecc0 --- /dev/null +++ b/src/krb5_plugin/common/utils.c @@ -0,0 +1,254 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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 "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <jansson.h> +#include <krb5/preauth_plugin.h> + +#include "krb5_plugin/common/utils.h" + +void +sss_string_array_free(char **array) +{ + size_t i; + + if (array == NULL) { + return; + } + + for (i = 0; array[i] != NULL; i++) { + free(array[i]); + } + + free(array); +} + +char ** +sss_string_array_copy(char **array) +{ + char **copy; + size_t i; + + for (i = 0; array[i] != NULL; i++) { + /* Just count. */ + } + + copy = calloc(i + 1, sizeof(char *)); + if (copy == NULL) { + return NULL; + } + + for (i = 0; array[i] != NULL; i++) { + copy[i] = strdup(array[i]); + if (copy[i] == NULL) { + sss_string_array_free(copy); + return NULL; + } + } + + copy[i] = NULL; + + return copy; +} + +char ** +sss_json_array_to_strings(json_t *jarray) +{ + const char *strval; + char **array; + json_t *jval; + size_t i; + + if (!json_is_array(jarray)) { + return NULL; + } + + array = calloc(json_array_size(jarray) + 1, sizeof(char *)); + if (array == NULL) { + return NULL; + } + + json_array_foreach(jarray, i, jval) { + strval = json_string_value(jval); + if (strval == NULL) { + goto fail; + } + + array[i] = strdup(strval); + if (array[i] == NULL) { + goto fail; + } + } + + return array; + +fail: + sss_string_array_free(array); + + return NULL; +} + +json_t * +sss_strings_to_json_array(char **array) +{ + json_t *jarray; + json_t *jstr; + size_t i; + int jret; + + jarray = json_array(); + if (jarray == NULL) { + return NULL; + } + + if (array == NULL) { + return jarray; + } + + for (i = 0; array[i] != NULL; i++) { + jstr = json_string(array[i]); + if (jstr == NULL) { + goto fail; + } + + jret = json_array_append_new(jarray, jstr); + if (jret != 0) { + goto fail; + } + } + + return jarray; + +fail: + json_decref(jarray); + + return NULL; +} + +void * +sss_radius_message_decode(const char *prefix, + sss_radius_message_decode_fn fn, + const char *str) +{ + size_t prefix_len; + + if (str == NULL) { + return NULL; + } + + prefix_len = strlen(prefix); + if (strncmp(str, prefix, prefix_len) != 0) { + return NULL; + } + + return fn(str + prefix_len); +} + +char * +sss_radius_message_encode(const char *prefix, + sss_radius_message_encode_fn fn, + const void *data) +{ + char *json_str; + char *str; + int aret; + + json_str = fn(data); + if (json_str == NULL) { + return NULL; + } + + aret = asprintf(&str, "%s%s", prefix, json_str); + free(json_str); + if (aret < 0) { + return NULL; + } + + return str; +} + +krb5_pa_data * +sss_radius_encode_padata(krb5_preauthtype patype, + sss_radius_message_encode_fn fn, + const void *data) +{ + krb5_pa_data *padata; + char *str; + + str = fn(data); + if (str == NULL) { + return NULL; + } + + padata = malloc(sizeof(krb5_pa_data)); + if (padata == NULL) { + free(str); + return NULL; + } + + padata->pa_type = patype; + padata->contents = (krb5_octet*)str; + padata->length = strlen(str) + 1; + + return padata; +} + +void * +sss_radius_decode_padata(sss_radius_message_decode_fn fn, + krb5_pa_data *padata) +{ + if (padata->length == 0 || padata->contents == NULL) { + return NULL; + } + + /* contents is NULL terminated string */ + if (padata->contents[padata->length - 1] != '\0') { + return NULL; + } + + return fn((const char*)padata->contents); +} + +krb5_pa_data ** +sss_radius_encode_padata_array(krb5_preauthtype patype, + sss_radius_message_encode_fn fn, + const void *data) +{ + krb5_pa_data **array; + + array = calloc(2, sizeof(krb5_pa_data *)); + if (array == NULL) { + return NULL; + } + + array[0] = sss_radius_encode_padata(patype, fn, data); + array[1] = NULL; + + if (array[0] == NULL) { + free(array); + return NULL; + } + + return array; +} diff --git a/src/krb5_plugin/common/utils.h b/src/krb5_plugin/common/utils.h new file mode 100644 index 0000000..b7042c6 --- /dev/null +++ b/src/krb5_plugin/common/utils.h @@ -0,0 +1,68 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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/>. +*/ + +#ifndef _KRB5_PLUGIN_UTILS_H_ +#define _KRB5_PLUGIN_UTILS_H_ + +#include <jansson.h> +#include <krb5/preauth_plugin.h> + +#define is_empty(var) ((var) == NULL || (var)[0] == '\0') + +void +sss_string_array_free(char **array); + +char ** +sss_string_array_copy(char **array); + +char ** +sss_json_array_to_strings(json_t *jarray); + +json_t * +sss_strings_to_json_array(char **array); + +typedef void * (*sss_radius_message_decode_fn)(const char *); +typedef char * (*sss_radius_message_encode_fn)(const void *); + +void * +sss_radius_message_decode(const char *prefix, + sss_radius_message_decode_fn fn, + const char *str); + +char * +sss_radius_message_encode(const char *prefix, + sss_radius_message_encode_fn fn, + const void *data); + +krb5_pa_data * +sss_radius_encode_padata(krb5_preauthtype patype, + sss_radius_message_encode_fn fn, + const void *data); + +void * +sss_radius_decode_padata(sss_radius_message_decode_fn fn, + krb5_pa_data *padata); + +krb5_pa_data ** +sss_radius_encode_padata_array(krb5_preauthtype patype, + sss_radius_message_encode_fn fn, + const void *data); + +#endif /* _KRB5_PLUGIN_UTILS_H_ */ diff --git a/src/krb5_plugin/idp/idp.h b/src/krb5_plugin/idp/idp.h new file mode 100644 index 0000000..c964580 --- /dev/null +++ b/src/krb5_plugin/idp/idp.h @@ -0,0 +1,70 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 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/>. +*/ + +#ifndef _IDP_H_ +#define _IDP_H_ + +#include <stdlib.h> +#include <krb5/preauth_plugin.h> + +#ifndef discard_const +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) +#endif + +#define SSSD_IDP_PLUGIN "idp" +#define SSSD_IDP_CONFIG "idp" +#define SSSD_IDP_OAUTH2_PADATA 152 // PA-REDHAT-IDP-OAUTH2 +#define SSSD_IDP_OAUTH2_QUESTION "idp-oauth2" +#define SSSD_IDP_OAUTH2_PREFIX "oauth2 " + +struct sss_idp_config { + char *type; + char **indicators; +}; + +void +sss_idp_config_free(struct sss_idp_config *idpcfg); + +krb5_error_code +sss_idp_config_init(const char *config, + struct sss_idp_config **_idpcfg); + +struct sss_idp_oauth2 { + char *verification_uri; + char *verification_uri_complete; + char *user_code; +}; + +void +sss_idp_oauth2_free(struct sss_idp_oauth2 *data); + +krb5_pa_data * +sss_idp_oauth2_encode_padata(struct sss_idp_oauth2 *data); + +struct sss_idp_oauth2 * +sss_idp_oauth2_decode_padata(krb5_pa_data *padata); + +char * +sss_idp_oauth2_encode_challenge(struct sss_idp_oauth2 *data); + +struct sss_idp_oauth2 * +sss_idp_oauth2_decode_challenge(const char *str); + +#endif /* _IDP_H_ */ diff --git a/src/krb5_plugin/idp/idp_clpreauth.c b/src/krb5_plugin/idp/idp_clpreauth.c new file mode 100644 index 0000000..91fe346 --- /dev/null +++ b/src/krb5_plugin/idp/idp_clpreauth.c @@ -0,0 +1,228 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 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 "config.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <krb5/clpreauth_plugin.h> + +#include "krb5_plugin/common/radius_clpreauth.h" +#include "idp.h" + +static krb5_pa_data ** +sss_idpcl_encode_padata(void) +{ + krb5_pa_data **padata; + + padata = calloc(2, sizeof(krb5_pa_data *)); + if (padata == NULL) { + return NULL; + } + + padata[0] = malloc(sizeof(krb5_pa_data)); + if (padata[0] == NULL) { + free(padata); + return NULL; + } + + padata[0]->pa_type = SSSD_IDP_OAUTH2_PADATA; + padata[0]->contents = NULL; + padata[0]->length = 0; + + padata[1] = NULL; + + return padata; +} + +static krb5_error_code +sss_idpcl_prompt(krb5_context context, + krb5_prompter_fct prompter, + void *prompter_data, + struct sss_idp_oauth2 *data, + krb5_data *_reply) +{ + krb5_error_code ret; + krb5_prompt prompt; + char *prompt_str; + int aret; + + if (data->verification_uri_complete != NULL) { + aret = asprintf(&prompt_str, + "Authenticate at %1$s and press ENTER.", + data->verification_uri_complete); + } else { + aret = asprintf(&prompt_str, + "Authenticate with PIN %1$s at %2$s and press ENTER.", + data->user_code, data->verification_uri); + } + + if (aret < 0) { + return ENOMEM; + } + + prompt.reply = _reply; + prompt.prompt = prompt_str; + prompt.hidden = 1; + + ret = (*prompter)(context, prompter_data, NULL, NULL, 1, &prompt); + free(prompt_str); + + return ret; +} + +static krb5_error_code +sss_idpcl_prep_questions(krb5_context context, + krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq modreq, + krb5_get_init_creds_opt *opt, + krb5_clpreauth_callbacks cb, + krb5_clpreauth_rock rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data) +{ + struct sss_idp_oauth2 *data; + char *challenge = NULL; + krb5_error_code ret; + + data = sss_idp_oauth2_decode_padata(pa_data); + if (data == NULL) { + ret = ENOMEM; + goto done; + } + + challenge = sss_idp_oauth2_encode_challenge(data); + if (challenge == NULL) { + ret = ENOMEM; + goto done; + } + + ret = cb->ask_responder_question(context, rock, SSSD_IDP_OAUTH2_QUESTION, + challenge); + +done: + sss_idp_oauth2_free(data); + free(challenge); + + return ret; +} + +static krb5_error_code +sss_idpcl_process(krb5_context context, + krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq modreq, + krb5_get_init_creds_opt *opt, + krb5_clpreauth_callbacks cb, + krb5_clpreauth_rock rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_pa_data ***_pa_data_out) +{ + krb5_keyblock *as_key; + krb5_pa_data **padata; + krb5_error_code ret; + krb5_data reply; + struct sss_idp_oauth2 *data = NULL; + char prompt_answer[255] = {0}; + const char *answer; + + data = sss_idp_oauth2_decode_padata(pa_data); + if (data == NULL) { + ret = ENOMEM; + goto done; + } + + /* Get FAST armor key. */ + as_key = cb->fast_armor(context, rock); + if (as_key == NULL) { + ret = ENOENT; + goto done; + } + + answer = cb->get_responder_answer(context, rock, SSSD_IDP_OAUTH2_QUESTION); + /* Call prompter if we have no answer. We don't really require any answer, + * but we need to present a prompt to the user and wait until the user has + * finished authentication via an idp provider. */ + if (answer == NULL) { + reply.magic = 0; + reply.length = sizeof(prompt_answer) / sizeof(char); + reply.data = prompt_answer; + + ret = sss_idpcl_prompt(context, prompter, prompter_data, data, &reply); + if (ret != 0) { + goto done; + } + } + + /* Use FAST armor key as response key. */ + ret = cb->set_as_key(context, rock, as_key); + if (ret != 0) { + goto done; + } + + /* Encode the request into the pa_data output. */ + padata = sss_idpcl_encode_padata(); + if (padata == NULL) { + ret = ENOMEM; + goto done; + } + + cb->disable_fallback(context, rock); + *_pa_data_out = padata; + + ret = 0; + +done: + sss_idp_oauth2_free(data); + return ret; +} + +krb5_error_code +clpreauth_idp_initvt(krb5_context context, + int maj_ver, + int min_ver, + krb5_plugin_vtable vtable) +{ + static krb5_preauthtype pa_type_list[] = { SSSD_IDP_OAUTH2_PADATA, 0 }; + krb5_clpreauth_vtable vt; + + if (maj_ver != 1) { + return KRB5_PLUGIN_VER_NOTSUPP; + } + + vt = (krb5_clpreauth_vtable)vtable; + vt->name = discard_const(SSSD_IDP_PLUGIN); + vt->pa_type_list = pa_type_list; + vt->request_init = sss_radiuscl_init; + vt->prep_questions = sss_idpcl_prep_questions; + vt->process = sss_idpcl_process; + vt->request_fini = sss_radiuscl_fini; + vt->gic_opts = NULL; + + return 0; +} diff --git a/src/krb5_plugin/idp/idp_kdcpreauth.c b/src/krb5_plugin/idp/idp_kdcpreauth.c new file mode 100644 index 0000000..7d6d032 --- /dev/null +++ b/src/krb5_plugin/idp/idp_kdcpreauth.c @@ -0,0 +1,406 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 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 "config.h" + +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <krad.h> +#include <krb5/kdcpreauth_plugin.h> + +#include "shared/safealign.h" +#include "krb5_plugin/common/radius_kdcpreauth.h" +#include "krb5_plugin/common/utils.h" +#include "idp.h" +#include "util/util.h" + +struct sss_idpkdc_config { + struct sss_radiuskdc_config *radius; + struct sss_idp_config *idpcfg; +}; + +static void +sss_idpkdc_config_free(struct sss_idpkdc_config *config) +{ + if (config == NULL) { + return; + } + + sss_radiuskdc_config_free(config->radius); + sss_idp_config_free(config->idpcfg); + free(config); +} + +static krb5_error_code +sss_idpkdc_config_init(struct sss_radiuskdc_state *state, + krb5_context kctx, + krb5_const_principal princ, + const char *configstr, + struct sss_idpkdc_config **_config) +{ + struct sss_idpkdc_config *config; + krb5_error_code ret; + + if (state == NULL) { + return EINVAL; + } + + config = malloc(sizeof(struct sss_idpkdc_config)); + if (config == NULL) { + ret = ENOMEM; + goto done; + } + memset(config, 0, sizeof(struct sss_idpkdc_config)); + + ret = sss_radiuskdc_config_init(state, kctx, princ, configstr, &config->radius); + if (ret != 0) { + goto done; + } + + ret = sss_idp_config_init(configstr, &config->idpcfg); + if (ret != 0) { + goto done; + } + + *_config = config; + ret = 0; + +done: + if (ret != 0) { + sss_idpkdc_config_free(config); + } + + return ret; +} + +static void +sss_idpkdc_challenge_done(krb5_error_code rret, + const krad_packet *rreq, + const krad_packet *rres, + void *data) +{ + struct sss_radiuskdc_challenge *state; + struct sss_idp_oauth2 *idp_oauth2 = NULL; + krb5_pa_data *padata = NULL; + krb5_data rstate = {0}; + char *rmsg = NULL; + krb5_error_code ret; + + state = (struct sss_radiuskdc_challenge *)data; + + if (rret != 0) { + ret = rret; + goto done; + } + + if (krad_packet_get_code(rres) != krad_code_name2num("Access-Challenge")) { + ret = ENOENT; + goto done; + } + + ret = sss_radiuskdc_get_complete_attr(rres, "Proxy-State", &rstate); + if (ret != 0) { + goto done; + } + + rmsg = sss_radiuskdc_get_attr_as_string(rres, "Reply-Message"); + if (rmsg == NULL) { + ret = EINVAL; + goto done; + } + + /* Remember the RADIUS state so it can be set in the Access-Request message + * sent in sss_idpkdc_verify(), thus allowing the RADIUS server to + * associate the message with its internal state. */ + ret = sss_radiuskdc_set_cookie(state->kctx, state->cb, state->rock, + SSSD_IDP_OAUTH2_PADATA, &rstate); + if (ret != 0) { + goto done; + } + + idp_oauth2 = sss_idp_oauth2_decode_challenge(rmsg); + if (idp_oauth2 == NULL) { + ret = ENOMEM; + goto done; + } + + padata = sss_idp_oauth2_encode_padata(idp_oauth2); + if (padata == NULL) { + ret = ENOMEM; + goto done; + } + + ret = 0; + +done: + state->respond(state->arg, ret, padata); + sss_radiuskdc_challenge_free(state); + sss_idp_oauth2_free(idp_oauth2); + free(rstate.data); + free(rmsg); + + /* padata should not be freed */ + + return; +} + +/* Send a password-less Access-Request and expect Access-Challenge response. */ +static krb5_error_code +sss_idpkdc_challenge_send(krb5_context kctx, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_edata_respond_fn respond, + void *arg, + struct sss_radiuskdc_config *config) +{ + struct sss_radiuskdc_challenge *state; + krb5_error_code ret; + + state = sss_radiuskdc_challenge_init(kctx, cb, rock, respond, arg, config); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + ret = krad_client_send(state->client->client, + krad_code_name2num("Access-Request"), + state->client->attrs, config->server, + config->secret, config->timeout, config->retries, + sss_idpkdc_challenge_done, state); + +done: + if (ret != 0) { + sss_radiuskdc_challenge_free(state); + } + + return ret; +} + +/* Send Access-Request with password and state set to indicate that the user has + * finished authentication against idp provider. We expect Access-Accept. */ +static krb5_error_code +sss_idpkdc_verify_send(krb5_context kctx, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_callbacks cb, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_kdcpreauth_verify_respond_fn respond, + void *arg, + const krb5_data *rstate, + char **indicators, + struct sss_radiuskdc_config *config) +{ + struct sss_radiuskdc_verify *state; + krb5_error_code ret; + + state = sss_radiuskdc_verify_init(kctx, rock, cb, enc_tkt_reply, respond, + arg, indicators, config); + if (state == NULL) { + return ENOMEM; + } + + ret = sss_radiuskdc_put_complete_attr(state->client->attrs, + krad_attr_name2num("Proxy-State"), + rstate); + if (ret != 0) { + goto done; + } + + ret = krad_client_send(state->client->client, + krad_code_name2num("Access-Request"), + state->client->attrs, config->server, + config->secret, config->timeout, config->retries, + sss_radiuskdc_verify_done, state); + +done: + if (ret != 0) { + sss_radiuskdc_verify_free(state); + } + + return ret; +} + +static krb5_error_code +sss_idpkdc_init(krb5_context kctx, + krb5_kdcpreauth_moddata *_moddata, + const char **_realmnames) +{ + return sss_radiuskdc_init(SSSD_IDP_PLUGIN, kctx, _moddata, _realmnames); +} + +static void +sss_idpkdc_edata(krb5_context kctx, + krb5_kdc_req *request, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, + krb5_preauthtype pa_type, + krb5_kdcpreauth_edata_respond_fn respond, + void *arg) +{ + struct sss_idpkdc_config *config = NULL; + struct sss_radiuskdc_state *state; + krb5_keyblock *armor_key; + char *configstr = NULL; + krb5_error_code ret; + + state = (struct sss_radiuskdc_state *)moddata; + + ret = sss_radiuskdc_enabled(SSSD_IDP_CONFIG, kctx, cb, rock, &configstr); + if (ret != 0) { + goto done; + } + + armor_key = cb->fast_armor(kctx, rock); + if (armor_key == NULL) { + ret = ENOENT; + goto done; + } + + ret = sss_idpkdc_config_init(state, kctx, cb->client_name(kctx, rock), + configstr, &config); + if (ret != 0) { + goto done; + } + + ret = sss_idpkdc_challenge_send(kctx, cb, rock, respond, arg, + config->radius); + +done: + if (ret != 0) { + respond(arg, ret, NULL); + } + + cb->free_string(kctx, rock, configstr); + sss_idpkdc_config_free(config); +} + +static void +sss_idpkdc_verify(krb5_context kctx, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *pa, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, + krb5_kdcpreauth_verify_respond_fn respond, + void *arg) +{ + struct sss_radiuskdc_state *state; + struct sss_idpkdc_config *config = NULL; + char *configstr = NULL; + krb5_error_code ret; + krb5_data rstate; + + state = (struct sss_radiuskdc_state *)moddata; + + ret = sss_radiuskdc_enabled(SSSD_IDP_CONFIG, kctx, cb, rock, &configstr); + if (ret != 0) { + goto done; + } + + ret = sss_idpkdc_config_init(state, kctx, cb->client_name(kctx, rock), + configstr, &config); + if (ret != 0) { + goto done; + } + + ret = sss_radiuskdc_get_cookie(kctx, cb, rock, SSSD_IDP_OAUTH2_PADATA, + &rstate); + if (ret != 0) { + goto done; + } + + if (pa->pa_type != SSSD_IDP_OAUTH2_PADATA || pa->length != 0) { + ret = KRB5_PREAUTH_BAD_TYPE; + goto done; + } + + /* config is freed by verify_done if ret == 0 */ + ret = sss_idpkdc_verify_send(kctx, rock, cb, enc_tkt_reply, respond, arg, + &rstate, config->idpcfg->indicators, config->radius); + if (ret != 0) { + goto done; + } + + ret = 0; + +done: + if (ret != 0) { + respond(arg, ret, NULL, NULL, NULL); + } + + cb->free_string(kctx, rock, configstr); + sss_idpkdc_config_free(config); +} + +krb5_error_code +sss_idpkdc_return_padata(krb5_context kctx, + krb5_pa_data *padata, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa_out, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, + krb5_kdcpreauth_modreq modreq) +{ + /* Unexpected padata. Return error. */ + if (padata->length != 0) { + return EINVAL; + } + + return sss_radiuskdc_return_padata(kctx, padata, req_pkt, request, reply, + encrypting_key, send_pa_out, cb, rock, + moddata, modreq); +} + +krb5_error_code +kdcpreauth_idp_initvt(krb5_context kctx, + int maj_ver, + int min_ver, + krb5_plugin_vtable vtable) +{ + static krb5_preauthtype pa_type_list[] = { SSSD_IDP_OAUTH2_PADATA, 0 }; + krb5_kdcpreauth_vtable vt; + + if (maj_ver != 1) { + return KRB5_PLUGIN_VER_NOTSUPP; + } + + vt = (krb5_kdcpreauth_vtable)vtable; + vt->name = discard_const(SSSD_IDP_PLUGIN); + vt->pa_type_list = pa_type_list; + vt->init = sss_idpkdc_init; + vt->fini = sss_radiuskdc_fini; + vt->flags = sss_radiuskdc_flags; + vt->edata = sss_idpkdc_edata; + vt->verify = sss_idpkdc_verify; + vt->return_padata = sss_idpkdc_return_padata; + + com_err(SSSD_IDP_PLUGIN, 0, "SSSD IdP plugin loaded"); + + return 0; +} diff --git a/src/krb5_plugin/idp/idp_utils.c b/src/krb5_plugin/idp/idp_utils.c new file mode 100644 index 0000000..90424d0 --- /dev/null +++ b/src/krb5_plugin/idp/idp_utils.c @@ -0,0 +1,273 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 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 "config.h" + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <jansson.h> +#include <arpa/inet.h> +#include <krb5/preauth_plugin.h> + +#include "krb5_plugin/common/utils.h" +#include "krb5_plugin/idp/idp.h" + +void +sss_idp_config_free(struct sss_idp_config *idpcfg) +{ + if (idpcfg == NULL) { + return; + } + + if (idpcfg->type != NULL) { + free(idpcfg->type); + } + + sss_string_array_free(idpcfg->indicators); + free(idpcfg); +} + +/** + * [{ + * "type": "oauth2", + * "indicators": ["..."] (optional) + * }] + * + * Note: array and type is used for future extensibility. + */ +krb5_error_code +sss_idp_config_init(const char *config, + struct sss_idp_config **_idpcfg) +{ + struct sss_idp_config *idpcfg; + json_t *jindicators = NULL; + json_error_t jret; + json_t *jroot; + krb5_error_code ret; + + idpcfg = malloc(sizeof(struct sss_idp_config)); + if (idpcfg == NULL) { + return ENOMEM; + } + memset(idpcfg, 0, sizeof(struct sss_idp_config)); + + jroot = json_loads(config, 0, &jret); + if (jroot == NULL) { + ret = EINVAL; + goto done; + } + + /* Only one item is supported at the moment. The rest is ignored. */ + ret = json_unpack(jroot, "[{s:s, s?:o}]", + "type", &idpcfg->type, + "indicators", &jindicators); + if (ret != 0) { + ret = EINVAL; + goto done; + } + + /* Only oauth2 type is supported at the moment. */ + if (strcmp(idpcfg->type, "oauth2") != 0) { + ret = EINVAL; + goto done; + } + + idpcfg->type = strdup(idpcfg->type); + if (idpcfg->type == NULL) { + ret = ENOMEM; + goto done; + } + + /* Are indicators set? */ + if (jindicators != NULL) { + idpcfg->indicators = sss_json_array_to_strings(jindicators); + if (idpcfg->indicators == NULL) { + ret = EINVAL; + goto done; + } + } + + *_idpcfg = idpcfg; + + ret = 0; + +done: + if (ret != 0) { + sss_idp_config_free(idpcfg); + } + + if (jroot != NULL) { + json_decref(jroot); + } + + return ret; +} + +void +sss_idp_oauth2_free(struct sss_idp_oauth2 *data) +{ + if (data == NULL) { + return; + } + + free(data->verification_uri); + free(data->verification_uri_complete); + free(data->user_code); + free(data); +} + +static struct sss_idp_oauth2 * +sss_idp_oauth2_init(const char *verification_uri, + const char *verification_uri_complete, + const char *user_code) +{ + struct sss_idp_oauth2 *data; + + /* These are required fields. */ + if (is_empty(verification_uri) || is_empty(user_code)) { + return NULL; + } + + data = malloc(sizeof(struct sss_idp_oauth2)); + if (data == NULL) { + return NULL; + } + memset(data, 0, sizeof(struct sss_idp_oauth2)); + + data->verification_uri = strdup(verification_uri); + data->user_code = strdup(user_code); + if (data->verification_uri == NULL || data->user_code == NULL) { + sss_idp_oauth2_free(data); + return NULL; + } + + if (!is_empty(verification_uri_complete)) { + data->verification_uri_complete = strdup(verification_uri_complete); + if (data->verification_uri_complete == NULL) { + sss_idp_oauth2_free(data); + return NULL; + } + } + + return data; +} + +static struct sss_idp_oauth2 * +sss_idp_oauth2_from_json(const char *json_str) +{ + struct sss_idp_oauth2 jdata = {0}; + struct sss_idp_oauth2 *data; + json_error_t jret; + json_t *jroot; + int ret; + + jroot = json_loads(json_str, 0, &jret); + if (jroot == NULL) { + return NULL; + } + + ret = json_unpack(jroot, "{s:s, s?:s, s:s}", + "verification_uri", &jdata.verification_uri, + "verification_uri_complete", &jdata.verification_uri_complete, + "user_code", &jdata.user_code); + if (ret != 0) { + json_decref(jroot); + return NULL; + } + + data = sss_idp_oauth2_init(jdata.verification_uri, + jdata.verification_uri_complete, + jdata.user_code); + + json_decref(jroot); + return data; +} + +static char * +sss_idp_oauth2_to_json(const struct sss_idp_oauth2 *data) +{ + json_t *jroot; + char *str; + + if (data == NULL) { + return NULL; + } + + /* These are required fields. */ + if (data->verification_uri == NULL || data->user_code == NULL) { + return NULL; + } + + jroot = json_pack("{s:s?, s:s*, s:s?}", + "verification_uri", data->verification_uri, + "verification_uri_complete", data->verification_uri_complete, + "user_code", data->user_code); + if (jroot == NULL) { + return NULL; + } + + str = json_dumps(jroot, JSON_COMPACT); + json_decref(jroot); + + return str; +} + +static struct sss_idp_oauth2 * +sss_idp_oauth2_decode(const char *str) +{ + return sss_radius_message_decode(SSSD_IDP_OAUTH2_PREFIX, + (sss_radius_message_decode_fn)sss_idp_oauth2_from_json, str); +} + +static char * +sss_idp_oauth2_encode(struct sss_idp_oauth2 *data) +{ + return sss_radius_message_encode(SSSD_IDP_OAUTH2_PREFIX, + (sss_radius_message_encode_fn)sss_idp_oauth2_to_json, data); +} + +krb5_pa_data * +sss_idp_oauth2_encode_padata(struct sss_idp_oauth2 *data) +{ + return sss_radius_encode_padata(SSSD_IDP_OAUTH2_PADATA, + (sss_radius_message_encode_fn)sss_idp_oauth2_encode, data); +} + +struct sss_idp_oauth2 * +sss_idp_oauth2_decode_padata(krb5_pa_data *padata) +{ + return sss_radius_decode_padata( + (sss_radius_message_decode_fn)sss_idp_oauth2_decode, padata); +} + +char * +sss_idp_oauth2_encode_challenge(struct sss_idp_oauth2 *data) +{ + return sss_idp_oauth2_encode(data); +} + +struct sss_idp_oauth2 * +sss_idp_oauth2_decode_challenge(const char *str) +{ + return sss_idp_oauth2_decode(str); +} diff --git a/src/krb5_plugin/idp/sssd_enable_idp b/src/krb5_plugin/idp/sssd_enable_idp new file mode 100644 index 0000000..8f98174 --- /dev/null +++ b/src/krb5_plugin/idp/sssd_enable_idp @@ -0,0 +1,14 @@ +# Enable SSSD OAuth2 Kerberos preauthentication plugins. +# +# This will allow you to obtain Kerberos TGT through OAuth2 authentication. +# +# To disable the OAuth2 plugin, comment out the following lines. + +[plugins] + clpreauth = { + module = idp:/usr/lib64/sssd/modules/sssd_krb5_idp_plugin.so + } + + kdcpreauth = { + module = idp:/usr/lib64/sssd/modules/sssd_krb5_idp_plugin.so + } diff --git a/src/krb5_plugin/passkey/passkey.h b/src/krb5_plugin/passkey/passkey.h new file mode 100644 index 0000000..0f36d5d --- /dev/null +++ b/src/krb5_plugin/passkey/passkey.h @@ -0,0 +1,110 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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/>. +*/ + +#ifndef _PASSKEY_H_ +#define _PASSKEY_H_ + +#include <stdlib.h> +#include <krb5/preauth_plugin.h> + +#ifndef discard_const +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) +#endif + +#define SSSD_PASSKEY_PLUGIN "passkey" +#define SSSD_PASSKEY_CONFIG "passkey" +#define SSSD_PASSKEY_PADATA 153 // PA-REDHAT-PASSKEY +#define SSSD_PASSKEY_QUESTION "passkey" +#define SSSD_PASSKEY_PREFIX "passkey " +#define SSSD_PASSKEY_REPLY_STATE "ipa_otpd state" +#define SSSD_PASSKEY_PROMPT "Insert your passkey device, then press ENTER" +#define SSSD_PASSKEY_PIN_PROMPT "Enter PIN" +#define SSSD_PASSKEY_CHILD SSSD_LIBEXEC_PATH"/passkey_child" + +struct sss_passkey_config { + char **indicators; +}; + +void +sss_passkey_config_free(struct sss_passkey_config *passkey); + +krb5_error_code +sss_passkey_config_init(const char *config, + struct sss_passkey_config **_passkey); + +enum sss_passkey_phase { + SSS_PASSKEY_PHASE_INIT, + SSS_PASSKEY_PHASE_CHALLENGE, + SSS_PASSKEY_PHASE_REPLY +}; + +struct sss_passkey_challenge { + char *domain; + char **credential_id_list; + int user_verification; + char *cryptographic_challenge; +}; + +struct sss_passkey_reply { + char *credential_id; + char *cryptographic_challenge; + char *authenticator_data; + char *assertion_signature; + char *user_id; +}; + +struct sss_passkey_message { + enum sss_passkey_phase phase; + char *state; + union { + struct sss_passkey_challenge *challenge; + struct sss_passkey_reply *reply; + void *ptr; + } data; +}; + +void +sss_passkey_message_free(struct sss_passkey_message *message); + +struct sss_passkey_message * +sss_passkey_message_from_reply_json(enum sss_passkey_phase phase, + const char *state, + const char *json_str); + +char * +sss_passkey_message_encode(const struct sss_passkey_message *data); + +struct sss_passkey_message * +sss_passkey_message_decode(const char *str); + +krb5_pa_data * +sss_passkey_message_encode_padata(const struct sss_passkey_message *data); + +struct sss_passkey_message * +sss_passkey_message_decode_padata(krb5_pa_data *padata); + +krb5_pa_data ** +sss_passkey_message_encode_padata_array(const struct sss_passkey_message *data); + +krb5_error_code +sss_passkey_concat_credentials(char **creds, + char **_creds_str); + +#endif /* _PASSKEY_H_ */ diff --git a/src/krb5_plugin/passkey/passkey_clpreauth.c b/src/krb5_plugin/passkey/passkey_clpreauth.c new file mode 100644 index 0000000..d2dfe6f --- /dev/null +++ b/src/krb5_plugin/passkey/passkey_clpreauth.c @@ -0,0 +1,395 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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 "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <sys/wait.h> +#include <unistd.h> +#include <krb5/clpreauth_plugin.h> + +#include "krb5_plugin/common/radius_clpreauth.h" +#include "passkey.h" + +#include "util/child_common.h" + +static krb5_error_code +sss_passkeycl_prompt(krb5_context context, + krb5_prompter_fct prompter, + void *prompter_data, + struct sss_passkey_message *message, + const char *prompt_txt, + char *prompt_answer, + int answer_len, + krb5_data *_reply) +{ + krb5_error_code ret; + krb5_prompt prompt; + char *prompt_str; + int aret; + + _reply->magic = 0; + _reply->length = answer_len; + _reply->data = prompt_answer; + + aret = asprintf(&prompt_str, "%s", prompt_txt); + if (aret < 0) { + return ENOMEM; + } + + prompt.reply = _reply; + prompt.prompt = prompt_str; + prompt.hidden = 1; + + ret = (*prompter)(context, prompter_data, NULL, NULL, 1, &prompt); + free(prompt_str); + + return ret; +} + +static krb5_error_code +sss_passkeycl_prep_questions(krb5_context context, + krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq modreq, + krb5_get_init_creds_opt *opt, + krb5_clpreauth_callbacks cb, + krb5_clpreauth_rock rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data) +{ + struct sss_passkey_message *message; + char *question = NULL; + char **realms = NULL; + krb5_error_code ret; + size_t r = 0; + + message = sss_passkey_message_decode_padata(pa_data); + if (message == NULL) { + ret = ENOMEM; + goto done; + } + + if (message->data.challenge->domain == NULL) { + ret = KRB5KDC_ERR_PREAUTH_FAILED; + goto done; + } + + /* Find a realm that matches the domain from the challenge */ + ret = krb5_get_host_realm(context, + message->data.challenge->domain, + &realms); + if (ret || ((realms == NULL) || (realms[0] == NULL))) { + ret = KRB5KDC_ERR_PREAUTH_FAILED; + goto done; + } + + /* Do explicit check for the challenge domain in case + * we've got back a referral (empty) realm */ + if (strlen(realms[0]) == strlen(KRB5_REFERRAL_REALM)) { + ret = strncasecmp(message->data.challenge->domain, + request->server->realm.data, + request->server->realm.length); + if (ret != 0) { + ret = KRB5KDC_ERR_PREAUTH_FAILED; + goto done; + } + + } else { + for(r = 0; realms[r] != NULL; r++) { + ret = strncasecmp(realms[r], + request->server->realm.data, + request->server->realm.length); + if (ret == 0) { + break; + } + } + + /* doesn't know the domain, reject the challenge */ + if (realms[r] == NULL) { + ret = KRB5KDC_ERR_PREAUTH_FAILED; + goto done; + } + } + + question = sss_passkey_message_encode(message); + if (question == NULL) { + ret = ENOMEM; + goto done; + } + + ret = cb->ask_responder_question(context, rock, SSSD_PASSKEY_QUESTION, + question); + +done: + if (realms) { + krb5_free_host_realm(context, realms); + } + sss_passkey_message_free(message); + free(question); + + return ret; +} + +static krb5_error_code +sss_passkeycl_exec_child(struct sss_passkey_challenge *data, + char *pin, + uint8_t **_reply) +{ + int pipe_to_child[2]; + int pipe_to_parent[2]; + pid_t cpid; + char *args[10] = {NULL}; + int arg_c = 0; + int size; + uint8_t *buf; + int ret = 0; + char *result_creds; + + buf = calloc(1, CHILD_MSG_CHUNK); + if (buf == NULL) { + ret = ENOMEM; + return ret; + } + + ret = sss_passkey_concat_credentials(data->credential_id_list, + &result_creds); + if (ret != 0) { + ret = ENOMEM; + goto done; + } + + args[arg_c++] = discard_const(SSSD_PASSKEY_CHILD); + args[arg_c++] = discard_const("--get-assert"); + args[arg_c++] = discard_const("--domain"); + args[arg_c++] = data->domain; + args[arg_c++] = discard_const("--key-handle"); + args[arg_c++] = discard_const(result_creds); + args[arg_c++] = discard_const("--cryptographic-challenge"); + args[arg_c++] = data->cryptographic_challenge; + args[arg_c++] = NULL; + + ret = pipe(pipe_to_child); + if (ret == -1) { + ret = ENOMEM; + goto done; + } + + ret = pipe(pipe_to_parent); + if (ret == -1) { + ret = ENOMEM; + goto done; + } + + cpid = fork(); + /* Child */ + if (cpid == 0) { + close(pipe_to_child[1]); + dup2(pipe_to_child[0], STDIN_FILENO); + + close(pipe_to_parent[0]); + dup2(pipe_to_parent[1], STDOUT_FILENO); + + execv(SSSD_PASSKEY_CHILD, args); + exit(EXIT_FAILURE); + /* Parent - write PIN to child and read output + * back from child */ + } else { + close(pipe_to_child[0]); + close(pipe_to_parent[1]); + + write(pipe_to_child[1], pin, strlen(pin)); + close(pipe_to_child[1]); + + size = read(pipe_to_parent[0], buf, CHILD_MSG_CHUNK); + if (size == -1) { + ret = ENOMEM; + goto done; + } + + close(pipe_to_parent[0]); + wait(NULL); + } + + *_reply = buf; + +done: + if (ret != 0) { + free(buf); + } + + free(result_creds); + return ret; +} + +static krb5_error_code +sss_passkeycl_process(krb5_context context, + krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq modreq, + krb5_get_init_creds_opt *opt, + krb5_clpreauth_callbacks cb, + krb5_clpreauth_rock rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *pa_data, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_pa_data ***_pa_data_out) +{ + krb5_keyblock *as_key; + krb5_pa_data **padata; + krb5_error_code ret; + krb5_data user_reply; + struct sss_passkey_message *input_message = NULL; + struct sss_passkey_message *reply_message = NULL; + struct sss_passkey_message *reply_msg = NULL; + enum sss_passkey_phase phase; + const char *state; + char prompt_answer[255] = {0}; + int answer_len; + char *prompt_reply = NULL; + uint8_t *reply = NULL; + const char *answer; + + input_message = sss_passkey_message_decode_padata(pa_data); + if (input_message == NULL) { + ret = ENOMEM; + goto done; + } + + /* Get FAST armor key. */ + as_key = cb->fast_armor(context, rock); + if (as_key == NULL) { + ret = ENOENT; + goto done; + } + + answer = cb->get_responder_answer(context, rock, SSSD_PASSKEY_QUESTION); + /* Call prompter if we have no answer to present a prompt. */ + if (answer == NULL) { + /* Interactive prompt */ + answer_len = sizeof(prompt_answer) / sizeof(char); + + ret = sss_passkeycl_prompt(context, prompter, prompter_data, + input_message, SSSD_PASSKEY_PROMPT, + prompt_answer, answer_len, + &user_reply); + if (ret != 0) { + goto done; + } + + /* Prompt for PIN */ + if (input_message->data.challenge->user_verification == 1) { + ret = sss_passkeycl_prompt(context, prompter, prompter_data, + input_message, SSSD_PASSKEY_PIN_PROMPT, + prompt_answer, answer_len, + &user_reply); + if (ret != 0) { + goto done; + } + } + + ret = sss_passkeycl_exec_child(input_message->data.challenge, prompt_answer, &reply); + if (ret != 0) { + goto done; + } + + phase = SSS_PASSKEY_PHASE_REPLY; + state = SSSD_PASSKEY_REPLY_STATE; + reply_msg = sss_passkey_message_from_reply_json(phase, state, (char *)reply); + if (reply_msg == NULL) { + ret = ENOMEM; + goto done; + } + + prompt_reply = sss_passkey_message_encode(reply_msg); + if (prompt_reply == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* Use FAST armor key as response key. */ + ret = cb->set_as_key(context, rock, as_key); + if (ret != 0) { + goto done; + } + + /* Encode the answer into the pa_data output. */ + if (prompt_reply != NULL) { + reply_message = sss_passkey_message_decode(prompt_reply); + } else { + reply_message = sss_passkey_message_decode(answer); + } + if (reply_message == NULL) { + ret = ENOMEM; + goto done; + } + + padata = sss_passkey_message_encode_padata_array(reply_message); + if (padata == NULL) { + ret = ENOMEM; + goto done; + } + + cb->disable_fallback(context, rock); + *_pa_data_out = padata; + + ret = 0; + +done: + sss_passkey_message_free(reply_message); + sss_passkey_message_free(reply_msg); + sss_passkey_message_free(input_message); + free(reply); + free(prompt_reply); + + return ret; +} + +krb5_error_code +clpreauth_passkey_initvt(krb5_context context, + int maj_ver, + int min_ver, + krb5_plugin_vtable vtable) +{ + static krb5_preauthtype pa_type_list[] = { SSSD_PASSKEY_PADATA, 0 }; + krb5_clpreauth_vtable vt; + + if (maj_ver != 1) { + return KRB5_PLUGIN_VER_NOTSUPP; + } + + vt = (krb5_clpreauth_vtable)vtable; + vt->name = discard_const(SSSD_PASSKEY_PLUGIN); + vt->pa_type_list = pa_type_list; + vt->request_init = sss_radiuscl_init; + vt->prep_questions = sss_passkeycl_prep_questions; + vt->process = sss_passkeycl_process; + vt->request_fini = sss_radiuscl_fini; + vt->gic_opts = NULL; + + return 0; +} diff --git a/src/krb5_plugin/passkey/passkey_kdcpreauth.c b/src/krb5_plugin/passkey/passkey_kdcpreauth.c new file mode 100644 index 0000000..903cb42 --- /dev/null +++ b/src/krb5_plugin/passkey/passkey_kdcpreauth.c @@ -0,0 +1,438 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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 "config.h" + +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <krad.h> +#include <krb5/kdcpreauth_plugin.h> + +#include "shared/safealign.h" +#include "krb5_plugin/common/radius_kdcpreauth.h" +#include "krb5_plugin/common/utils.h" +#include "krb5_plugin/passkey/passkey.h" +#include "util/util.h" + +struct sss_passkeykdc_config { + struct sss_radiuskdc_config *radius; + struct sss_passkey_config *passkey; +}; + +static void +sss_passkeykdc_config_free(struct sss_passkeykdc_config *config) +{ + if (config == NULL) { + return; + } + + sss_radiuskdc_config_free(config->radius); + sss_passkey_config_free(config->passkey); + free(config); +} + +static krb5_error_code +sss_passkeykdc_config_init(struct sss_radiuskdc_state *state, + krb5_context kctx, + krb5_const_principal princ, + const char *configstr, + struct sss_passkeykdc_config **_config) +{ + struct sss_passkeykdc_config *config; + krb5_error_code ret; + + if (state == NULL) { + return EINVAL; + } + + config = malloc(sizeof(struct sss_passkeykdc_config)); + if (config == NULL) { + ret = ENOMEM; + goto done; + } + memset(config, 0, sizeof(struct sss_passkeykdc_config)); + + ret = sss_radiuskdc_config_init(state, kctx, princ, configstr, &config->radius); + if (ret != 0) { + goto done; + } + + ret = sss_passkey_config_init(configstr, &config->passkey); + if (ret != 0) { + goto done; + } + + + *_config = config; + ret = 0; + +done: + if (ret != 0) { + sss_passkeykdc_config_free(config); + } + + return ret; +} + +static void +sss_passkeykdc_challenge_done(krb5_error_code rret, + const krad_packet *rreq, + const krad_packet *rres, + void *data) +{ + struct sss_passkey_message *message = NULL; + struct sss_radiuskdc_challenge *state; + krb5_pa_data *padata = NULL; + krb5_data cookie = {0}; + char *reply = NULL; + krb5_error_code ret; + + state = (struct sss_radiuskdc_challenge *)data; + + if (rret != 0) { + ret = rret; + goto done; + } + + if (krad_packet_get_code(rres) != krad_code_name2num("Access-Challenge")) { + ret = ENOENT; + goto done; + } + + reply = sss_radiuskdc_get_attr_as_string(rres, "Proxy-State"); + if (reply == NULL) { + ret = EINVAL; + goto done; + } + + message = sss_passkey_message_decode(reply); + if (message == NULL) { + ret = ENOMEM; + goto done; + } + + if (message->phase != SSS_PASSKEY_PHASE_CHALLENGE) { + ret = EINVAL; + goto done; + } + + /* Remember the whole passkey challenge message in cookie so we can later + * verify that the client response is really associated with this request. + */ + cookie.data = reply; + cookie.length = strlen(reply) + 1; + ret = sss_radiuskdc_set_cookie(state->kctx, state->cb, state->rock, + SSSD_PASSKEY_PADATA, &cookie); + if (ret != 0) { + goto done; + } + + padata = sss_passkey_message_encode_padata(message); + if (padata == NULL) { + ret = ENOMEM; + goto done; + } + + ret = 0; + +done: + state->respond(state->arg, ret, padata); + sss_radiuskdc_challenge_free(state); + sss_passkey_message_free(message); + free(reply); + + /* padata should not be freed */ + + return; +} + +/* Send a password-less Access-Request and expect Access-Challenge response. */ +static krb5_error_code +sss_passkeykdc_challenge_send(krb5_context kctx, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_edata_respond_fn respond, + void *arg, + struct sss_radiuskdc_config *config) +{ + struct sss_passkey_message message; + struct sss_radiuskdc_challenge *state; + char *encoded_message = NULL; + krb5_error_code ret; + + state = sss_radiuskdc_challenge_init(kctx, cb, rock, respond, arg, config); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + message.phase = SSS_PASSKEY_PHASE_INIT; + message.state = NULL; + message.data.challenge = NULL; + + encoded_message = sss_passkey_message_encode(&message); + if (encoded_message == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_radiuskdc_set_attr_as_string(state->client->attrs, + "Proxy-State", + encoded_message); + if (ret != 0) { + goto done; + } + + ret = krad_client_send(state->client->client, + krad_code_name2num("Access-Request"), + state->client->attrs, config->server, + config->secret, config->timeout, config->retries, + sss_passkeykdc_challenge_done, state); + +done: + free(encoded_message); + + if (ret != 0) { + sss_radiuskdc_challenge_free(state); + } + + return ret; +} + +/* Send Access-Request with password and state set to indicate that the user has + * finished authentication against passkey provider. We expect Access-Accept. */ +static krb5_error_code +sss_passkeykdc_verify_send(krb5_context kctx, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_callbacks cb, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_kdcpreauth_verify_respond_fn respond, + void *arg, + const struct sss_passkey_message *message, + char **indicators, + struct sss_radiuskdc_config *config) +{ + struct sss_radiuskdc_verify *state; + char *encoded_message = NULL; + krb5_error_code ret; + + state = sss_radiuskdc_verify_init(kctx, rock, cb, enc_tkt_reply, respond, + arg, indicators, config); + if (state == NULL) { + return ENOMEM; + } + + encoded_message = sss_passkey_message_encode(message); + if (encoded_message == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_radiuskdc_set_attr_as_string(state->client->attrs, + "Proxy-State", + encoded_message); + if (ret != 0) { + goto done; + } + + ret = krad_client_send(state->client->client, + krad_code_name2num("Access-Request"), + state->client->attrs, config->server, + config->secret, config->timeout, config->retries, + sss_radiuskdc_verify_done, state); + +done: + free(encoded_message); + + if (ret != 0) { + sss_radiuskdc_verify_free(state); + } + + return ret; +} + +static krb5_error_code +sss_passkeykdc_init(krb5_context kctx, + krb5_kdcpreauth_moddata *_moddata, + const char **_realmnames) +{ + return sss_radiuskdc_init(SSSD_PASSKEY_PLUGIN, kctx, _moddata, _realmnames); +} + +static void +sss_passkeykdc_edata(krb5_context kctx, + krb5_kdc_req *request, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, + krb5_preauthtype pa_type, + krb5_kdcpreauth_edata_respond_fn respond, + void *arg) +{ + struct sss_passkeykdc_config *config = NULL; + struct sss_radiuskdc_state *state; + krb5_keyblock *armor_key; + char *configstr = NULL; + krb5_error_code ret; + + state = (struct sss_radiuskdc_state *)moddata; + + ret = sss_radiuskdc_enabled(SSSD_PASSKEY_CONFIG, kctx, cb, rock, &configstr); + if (ret != 0) { + goto done; + } + + armor_key = cb->fast_armor(kctx, rock); + if (armor_key == NULL) { + ret = ENOENT; + goto done; + } + + ret = sss_passkeykdc_config_init(state, kctx, cb->client_name(kctx, rock), + configstr, &config); + if (ret != 0) { + goto done; + } + + ret = sss_passkeykdc_challenge_send(kctx, cb, rock, respond, arg, + config->radius); + +done: + if (ret != 0) { + respond(arg, ret, NULL); + } + + cb->free_string(kctx, rock, configstr); + sss_passkeykdc_config_free(config); +} + +static void +sss_passkeykdc_verify(krb5_context kctx, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *pa, + krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, + krb5_kdcpreauth_verify_respond_fn respond, + void *arg) +{ + struct sss_radiuskdc_state *state; + struct sss_passkeykdc_config *config = NULL; + struct sss_passkey_message *message = NULL; + struct sss_passkey_message *challenge = NULL; + char *configstr = NULL; + krb5_error_code ret; + krb5_data cookie; + + state = (struct sss_radiuskdc_state *)moddata; + + ret = sss_radiuskdc_enabled(SSSD_PASSKEY_CONFIG, kctx, cb, rock, &configstr); + if (ret != 0) { + goto done; + } + + ret = sss_passkeykdc_config_init(state, kctx, cb->client_name(kctx, rock), + configstr, &config); + if (ret != 0) { + goto done; + } + + ret = sss_radiuskdc_get_cookie(kctx, cb, rock, SSSD_PASSKEY_PADATA, + &cookie); + if (ret != 0) { + goto done; + } + + challenge = sss_passkey_message_decode(cookie.data); + if (challenge == NULL) { + ret = ENOMEM; + goto done; + } + + if (pa->pa_type != SSSD_PASSKEY_PADATA || pa->length == 0) { + ret = KRB5_PREAUTH_BAD_TYPE; + goto done; + } + + message = sss_passkey_message_decode_padata(pa); + if (message == NULL) { + ret = ENOMEM; + goto done; + } + + if (message->phase != SSS_PASSKEY_PHASE_REPLY + || strcmp(message->state, challenge->state) != 0 + || strcmp(message->data.reply->cryptographic_challenge, + challenge->data.challenge->cryptographic_challenge) != 0) { + ret = EINVAL; + goto done; + } + + ret = sss_passkeykdc_verify_send(kctx, rock, cb, enc_tkt_reply, respond, + arg, message, config->passkey->indicators, config->radius); + if (ret != 0) { + goto done; + } + + ret = 0; + +done: + if (ret != 0) { + respond(arg, ret, NULL, NULL, NULL); + } + + cb->free_string(kctx, rock, configstr); + sss_passkeykdc_config_free(config); + sss_passkey_message_free(message); + sss_passkey_message_free(challenge); +} + +krb5_error_code +kdcpreauth_passkey_initvt(krb5_context kctx, + int maj_ver, + int min_ver, + krb5_plugin_vtable vtable) +{ + static krb5_preauthtype pa_type_list[] = { SSSD_PASSKEY_PADATA, 0 }; + krb5_kdcpreauth_vtable vt; + + if (maj_ver != 1) { + return KRB5_PLUGIN_VER_NOTSUPP; + } + + vt = (krb5_kdcpreauth_vtable)vtable; + vt->name = discard_const(SSSD_PASSKEY_PLUGIN); + vt->pa_type_list = pa_type_list; + vt->init = sss_passkeykdc_init; + vt->fini = sss_radiuskdc_fini; + vt->flags = sss_radiuskdc_flags; + vt->edata = sss_passkeykdc_edata; + vt->verify = sss_passkeykdc_verify; + vt->return_padata = sss_radiuskdc_return_padata; + + com_err(SSSD_PASSKEY_PLUGIN, 0, "SSSD passkey plugin loaded"); + + return 0; +} diff --git a/src/krb5_plugin/passkey/passkey_utils.c b/src/krb5_plugin/passkey/passkey_utils.c new file mode 100644 index 0000000..e73da97 --- /dev/null +++ b/src/krb5_plugin/passkey/passkey_utils.c @@ -0,0 +1,648 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2023 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 "config.h" + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <jansson.h> +#include <arpa/inet.h> +#include <krb5/preauth_plugin.h> + +#include "krb5_plugin/common/utils.h" +#include "krb5_plugin/passkey/passkey.h" + +void +sss_passkey_config_free(struct sss_passkey_config *passkey) +{ + if (passkey == NULL) { + return; + } + + sss_string_array_free(passkey->indicators); + free(passkey); +} + +/** + * { + * "indicators": ["..."] (optional) + * } + */ +krb5_error_code +sss_passkey_config_init(const char *config, + struct sss_passkey_config **_passkey) +{ + struct sss_passkey_config *passkey; + json_t *jindicators = NULL; + json_error_t jret; + json_t *jroot; + krb5_error_code ret; + + passkey = malloc(sizeof(struct sss_passkey_config)); + if (passkey == NULL) { + return ENOMEM; + } + memset(passkey, 0, sizeof(struct sss_passkey_config)); + + jroot = json_loads(config, 0, &jret); + if (jroot == NULL) { + ret = EINVAL; + goto done; + } + + ret = json_unpack(jroot, "[{s?:o}]", "indicators", &jindicators); + if (ret != 0) { + ret = EINVAL; + goto done; + } + + /* Are indicators set? */ + if (jindicators != NULL) { + passkey->indicators = sss_json_array_to_strings(jindicators); + if (passkey->indicators == NULL) { + ret = EINVAL; + goto done; + } + } + + *_passkey = passkey; + + ret = 0; + +done: + if (ret != 0) { + sss_passkey_config_free(passkey); + } + + if (jroot != NULL) { + json_decref(jroot); + } + + return ret; +} + +void +sss_passkey_challenge_free(struct sss_passkey_challenge *data) +{ + if (data == NULL) { + return; + } + + free(data->domain); + free(data->cryptographic_challenge); + sss_string_array_free(data->credential_id_list); + + free(data); +} + +static struct sss_passkey_challenge * +sss_passkey_challenge_init(char *domain, + char **credential_id_list, + int user_verification, + char *cryptographic_challenge) +{ + struct sss_passkey_challenge *data; + krb5_error_code ret; + + /* These are required fields. */ + if (is_empty(domain) + || is_empty(cryptographic_challenge) + || credential_id_list == NULL || is_empty(credential_id_list[0])) { + return NULL; + } + + data = malloc(sizeof(struct sss_passkey_challenge)); + if (data == NULL) { + return NULL; + } + memset(data, 0, sizeof(struct sss_passkey_challenge)); + + data->user_verification = user_verification; + data->domain = strdup(domain); + data->cryptographic_challenge = strdup(cryptographic_challenge); + if (data->domain == NULL || data->cryptographic_challenge == NULL) { + ret = ENOMEM; + goto done; + } + + data->credential_id_list = sss_string_array_copy(credential_id_list); + if (data->credential_id_list == NULL) { + ret = ENOMEM; + goto done; + } + + ret = 0; + +done: + if (ret != 0) { + sss_passkey_challenge_free(data); + return NULL; + } + + return data; +} + +static struct sss_passkey_challenge * +sss_passkey_challenge_from_json_object(json_t *jobject) +{ + struct sss_passkey_challenge jdata = {0}; + struct sss_passkey_challenge *data = NULL; + json_t *jcredential_id_list = NULL; + char **credential_id_list = NULL; + int ret; + + if (jobject == NULL) { + return NULL; + } + + ret = json_unpack(jobject, "{s:s, s:o, s:i, s:s}", + "domain", &jdata.domain, + "credential_id_list", &jcredential_id_list, + "user_verification", &jdata.user_verification, + "cryptographic_challenge", &jdata.cryptographic_challenge); + if (ret != 0) { + return NULL; + } + + if (jcredential_id_list != NULL) { + credential_id_list = sss_json_array_to_strings(jcredential_id_list); + if (credential_id_list == NULL) { + return NULL; + } + } + + data = sss_passkey_challenge_init(jdata.domain, credential_id_list, + jdata.user_verification, + jdata.cryptographic_challenge); + + sss_string_array_free(credential_id_list); + return data; +} + +static json_t * +sss_passkey_challenge_to_json_object(const struct sss_passkey_challenge *data) +{ + json_t *jroot; + json_t *jcredential_id_list; + + if (data == NULL) { + return NULL; + } + + /* These are required fields. */ + if (data->domain == NULL || data->credential_id_list == NULL + || data->cryptographic_challenge == NULL) { + return NULL; + } + + jcredential_id_list = sss_strings_to_json_array(data->credential_id_list); + if (jcredential_id_list == NULL) { + return NULL; + } + + jroot = json_pack("{s:s, s:o, s:i, s:s}", "domain", data->domain, + "credential_id_list", jcredential_id_list, + "user_verification", data->user_verification, + "cryptographic_challenge", + data->cryptographic_challenge); + if (jroot == NULL) { + json_decref(jcredential_id_list); + return NULL; + } + + return jroot; +} + +void +sss_passkey_reply_free(struct sss_passkey_reply *data) +{ + if (data == NULL) { + return; + } + + free(data->credential_id); + free(data->cryptographic_challenge); + free(data->authenticator_data); + free(data->assertion_signature); + free(data->user_id); + free(data); +} + +static struct sss_passkey_reply * +sss_passkey_reply_init(char *credential_id, + char *cryptographic_challenge, + char *authenticator_data, + char *assertion_signature, + char *user_id) +{ + struct sss_passkey_reply *data; + krb5_error_code ret; + + /* These are required fields. */ + if (is_empty(credential_id) + || is_empty(cryptographic_challenge) + || is_empty(authenticator_data) + || is_empty(assertion_signature)) { + return NULL; + } + + data = malloc(sizeof(struct sss_passkey_reply)); + if (data == NULL) { + return NULL; + } + memset(data, 0, sizeof(struct sss_passkey_reply)); + + data->credential_id = strdup(credential_id); + data->cryptographic_challenge = strdup(cryptographic_challenge); + data->authenticator_data = strdup(authenticator_data); + data->assertion_signature = strdup(assertion_signature); + data->user_id = user_id == NULL ? NULL : strdup(user_id); + if (data->credential_id == NULL + || data->cryptographic_challenge == NULL + || data->authenticator_data == NULL + || data->assertion_signature == NULL + || (user_id != NULL && data->user_id == NULL)) { + ret = ENOMEM; + goto done; + } + + ret = 0; + +done: + if (ret != 0) { + sss_passkey_reply_free(data); + return NULL; + } + + return data; +} + +static struct sss_passkey_reply * +sss_passkey_reply_from_json_object(json_t *jobject) +{ + struct sss_passkey_reply jdata = {0}; + int ret; + + if (jobject == NULL) { + return NULL; + } + + ret = json_unpack(jobject, "{s:s, s:s, s:s, s:s, s?:s}", + "credential_id", &jdata.credential_id, + "cryptographic_challenge", &jdata.cryptographic_challenge, + "authenticator_data", &jdata.authenticator_data, + "assertion_signature", &jdata.assertion_signature, + "user_id", &jdata.user_id); + if (ret != 0) { + return NULL; + } + + return sss_passkey_reply_init(jdata.credential_id, + jdata.cryptographic_challenge, + jdata.authenticator_data, + jdata.assertion_signature, + jdata.user_id); +} + +static json_t * +sss_passkey_reply_to_json_object(const struct sss_passkey_reply *data) +{ + if (data == NULL) { + return NULL; + } + + /* These are required fields. */ + if (data->credential_id == NULL + || data->cryptographic_challenge == NULL + || data->authenticator_data == NULL + || data->assertion_signature == NULL) { + return NULL; + } + + return json_pack("{s:s, s:s, s:s, s:s, s:s*}", + "credential_id", data->credential_id, + "cryptographic_challenge", data->cryptographic_challenge, + "authenticator_data", data->authenticator_data, + "assertion_signature", data->assertion_signature, + "user_id", data->user_id); +} + +void +sss_passkey_message_free(struct sss_passkey_message *message) +{ + if (message == NULL) { + return; + } + + switch (message->phase) { + case SSS_PASSKEY_PHASE_INIT: + break; + case SSS_PASSKEY_PHASE_CHALLENGE: + sss_passkey_challenge_free(message->data.challenge); + break; + case SSS_PASSKEY_PHASE_REPLY: + sss_passkey_reply_free(message->data.reply); + break; + default: + /* nothing to do */ + break; + } + + free(message->state); + free(message); +} + +static struct sss_passkey_message * +sss_passkey_message_init(enum sss_passkey_phase phase, + const char *state, + void *data) +{ + struct sss_passkey_message *message; + + switch (phase) { + case SSS_PASSKEY_PHASE_INIT: + if (state != NULL || data != NULL) { + return NULL; + } + break; + case SSS_PASSKEY_PHASE_CHALLENGE: + case SSS_PASSKEY_PHASE_REPLY: + if (state == NULL || data == NULL) { + return NULL; + } + break; + default: + return NULL; + } + + message = malloc(sizeof(struct sss_passkey_message)); + if (message == NULL) { + return NULL; + } + memset(message, 0, sizeof(struct sss_passkey_message)); + + message->phase = phase; + message->state = state == NULL ? NULL : strdup(state); + message->data.ptr = data; + + if (state != NULL && message->state == NULL) { + sss_passkey_message_free(message); + return NULL; + } + + return message; +} + +static struct sss_passkey_message * +sss_passkey_message_from_json(const char *json_str) +{ + struct sss_passkey_message *message = NULL; + enum sss_passkey_phase phase = 0; + const char *state = NULL; + void *data = NULL; + json_error_t jret; + json_t *jdata = NULL; + json_t *jroot; + int ret; + + jroot = json_loads(json_str, 0, &jret); + if (jroot == NULL) { + return NULL; + } + + ret = json_unpack(jroot, "{s:i, s?:s, s?:o}", + "phase", &phase, + "state", &state, + "data", &jdata); + if (ret != 0) { + goto done; + } + + switch (phase) { + case SSS_PASSKEY_PHASE_INIT: + data = NULL; + break; + case SSS_PASSKEY_PHASE_CHALLENGE: + data = sss_passkey_challenge_from_json_object(jdata); + if (data == NULL) { + goto done; + } + break; + case SSS_PASSKEY_PHASE_REPLY: + data = sss_passkey_reply_from_json_object(jdata); + if (data == NULL) { + goto done; + } + break; + default: + goto done; + } + + message = sss_passkey_message_init(phase, state, data); + if (message == NULL && phase == SSS_PASSKEY_PHASE_CHALLENGE) { + sss_passkey_challenge_free(data); + } else if (message == NULL && phase == SSS_PASSKEY_PHASE_REPLY) { + sss_passkey_reply_free(data); + } + +done: + json_decref(jroot); + return message; +} + +static char * +sss_passkey_message_to_json(const struct sss_passkey_message *message) +{ + json_t *jroot; + json_t *jdata; + char *str; + + if (message == NULL) { + return NULL; + } + + switch (message->phase) { + case SSS_PASSKEY_PHASE_INIT: + if (message->state != NULL || message->data.ptr != NULL) { + return NULL; + } + jdata = NULL; + break; + case SSS_PASSKEY_PHASE_CHALLENGE: + if (message->state == NULL || message->data.challenge == NULL) { + return NULL; + } + + jdata = sss_passkey_challenge_to_json_object(message->data.challenge); + if (jdata == NULL) { + return NULL; + } + break; + case SSS_PASSKEY_PHASE_REPLY: + if (message->state == NULL || message->data.reply == NULL) { + return NULL; + } + + jdata = sss_passkey_reply_to_json_object(message->data.reply); + if (jdata == NULL) { + return NULL; + } + break; + default: + return NULL; + } + + jroot = json_pack("{s:i, s:s*, s:o*}", + "phase", message->phase, + "state", message->state, + "data", jdata); + if (jroot == NULL) { + json_decref(jdata); + return NULL; + } + + str = json_dumps(jroot, JSON_COMPACT); + json_decref(jroot); + + return str; +} + +struct sss_passkey_message * +sss_passkey_message_from_reply_json(enum sss_passkey_phase phase, + const char *state, + const char *json_str) +{ + json_error_t jret; + json_t *jroot; + struct sss_passkey_message *message; + struct sss_passkey_reply *data; + + if (json_str == NULL) { + return NULL; + } + + jroot = json_loads(json_str, 0, &jret); + if (jroot == NULL) { + return NULL; + } + + data = sss_passkey_reply_from_json_object(jroot); + if (data == NULL) { + json_decref(jroot); + return NULL; + } + + message = sss_passkey_message_init(phase, state, data); + if (message == NULL) { + sss_passkey_reply_free(data); + } + + json_decref(jroot); + return message; +} + +char * +sss_passkey_message_encode(const struct sss_passkey_message *data) +{ + return sss_radius_message_encode(SSSD_PASSKEY_PREFIX, + (sss_radius_message_encode_fn)sss_passkey_message_to_json, data); +} + +struct sss_passkey_message * +sss_passkey_message_decode(const char *str) +{ + return sss_radius_message_decode(SSSD_PASSKEY_PREFIX, + (sss_radius_message_decode_fn)sss_passkey_message_from_json, str); +} + +krb5_pa_data * +sss_passkey_message_encode_padata(const struct sss_passkey_message *data) +{ + return sss_radius_encode_padata(SSSD_PASSKEY_PADATA, + (sss_radius_message_encode_fn)sss_passkey_message_encode, data); +} + +struct sss_passkey_message * +sss_passkey_message_decode_padata(krb5_pa_data *padata) +{ + return sss_radius_decode_padata( + (sss_radius_message_decode_fn)sss_passkey_message_decode, padata); +} + +krb5_pa_data ** +sss_passkey_message_encode_padata_array(const struct sss_passkey_message *data) +{ + return sss_radius_encode_padata_array(SSSD_PASSKEY_PADATA, + (sss_radius_message_encode_fn)sss_passkey_message_encode, data); +} + +krb5_error_code +sss_passkey_concat_credentials(char **creds, + char **_creds_str) +{ + krb5_error_code ret; + char *result_creds = NULL; + size_t total_sz = 0; + size_t len = 0; + int rc = 0; + + for (int i = 0; creds[i] != NULL; i++) { + total_sz += strlen(creds[i]); + if (i > 0) { + /* separating comma in resulting creds string */ + total_sz++; + } + } + + result_creds = malloc(total_sz + 1); + if (result_creds == NULL) { + ret = ENOMEM; + goto done; + } + + len = strlen(creds[0]); + + rc = snprintf(result_creds, len + 1, "%s", creds[0]); + if (rc < 0 || rc > len) { + ret = ENOMEM; + free(result_creds); + goto done; + } + + for (int i = 1; creds[i] != NULL; i++) { + rc = snprintf(result_creds + len, total_sz - len + 1, ",%s", creds[i]); + if (rc < 0 || rc > total_sz - len) { + ret = ENOMEM; + free(result_creds); + goto done; + } + + len += rc; + } + + *_creds_str = result_creds; + + ret = 0; +done: + return ret; +} diff --git a/src/krb5_plugin/passkey/sssd_enable_passkey b/src/krb5_plugin/passkey/sssd_enable_passkey new file mode 100644 index 0000000..161e076 --- /dev/null +++ b/src/krb5_plugin/passkey/sssd_enable_passkey @@ -0,0 +1,14 @@ +# Enable SSSD Passkey Kerberos preauthentication plugins. +# +# This will allow you to obtain Kerberos TGT through passkey authentication. +# +# To disable the passkey plugin, comment out the following lines. + +[plugins] + clpreauth = { + module = passkey:/usr/lib64/sssd/modules/sssd_krb5_passkey_plugin.so + } + + kdcpreauth = { + module = passkey:/usr/lib64/sssd/modules/sssd_krb5_passkey_plugin.so + } diff --git a/src/krb5_plugin/sssd_krb5_localauth_plugin.c b/src/krb5_plugin/sssd_krb5_localauth_plugin.c new file mode 100644 index 0000000..1d995ff --- /dev/null +++ b/src/krb5_plugin/sssd_krb5_localauth_plugin.c @@ -0,0 +1,196 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2014 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 <nss.h> +#include <sys/types.h> +#include <pwd.h> +#include <string.h> +#include <strings.h> +#include <errno.h> + +#include <krb5/localauth_plugin.h> + +enum nss_status _nss_sss_getpwnam_r(const char *name, struct passwd *result, + char *buffer, size_t buflen, int *errnop); + +#define DEFAULT_BUFSIZE 4096 + +static krb5_error_code sss_userok(krb5_context context, + krb5_localauth_moddata data, + krb5_const_principal aname, + const char *lname) +{ + krb5_error_code kerr; + char *princ_str; + struct passwd pwd = { 0 }; + char *buffer = NULL; + size_t buflen; + enum nss_status nss_status; + int nss_errno; + uid_t princ_uid; + int ret; + struct passwd *result = NULL; + + kerr = krb5_unparse_name(context, aname, &princ_str); + if (kerr != 0) { + ret = kerr; + goto done; + } + + if (strcasecmp(princ_str, lname) == 0) { + ret = 0; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = malloc(buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + nss_status = _nss_sss_getpwnam_r(princ_str, &pwd, buffer, buflen, + &nss_errno); + if (nss_status != NSS_STATUS_SUCCESS) { + if (nss_status == NSS_STATUS_NOTFOUND) { + ret = KRB5_PLUGIN_NO_HANDLE; + } else { + ret = EIO; + } + goto done; + } + + princ_uid = pwd.pw_uid; + + ret = getpwnam_r(lname, &pwd, buffer, buflen, &result); + if (ret != 0 || result == NULL) { + if (result == NULL) { + ret = KRB5_PLUGIN_NO_HANDLE; + } else { + ret = EIO; + } + goto done; + } + + if (princ_uid != pwd.pw_uid) { + ret = EPERM; + goto done; + } + + ret = 0; + +done: + krb5_free_unparsed_name(context, princ_str); + free(buffer); + + if (ret != 0) { + return KRB5_PLUGIN_NO_HANDLE; + } + + return ret; +} + +static krb5_error_code sss_an2ln(krb5_context context, + krb5_localauth_moddata data, + const char *type, const char *residual, + krb5_const_principal aname, char **lname_out) +{ + krb5_error_code kerr; + char *princ_str; + struct passwd pwd = { 0 }; + char *buffer = NULL; + size_t buflen; + enum nss_status nss_status; + int nss_errno; + int ret; + char *str; + + kerr = krb5_unparse_name(context, aname, &princ_str); + if (kerr != 0) { + return kerr; + } + + buflen = DEFAULT_BUFSIZE; + buffer = malloc(buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + nss_status = _nss_sss_getpwnam_r(princ_str, &pwd, buffer, buflen, + &nss_errno); + if (nss_status != NSS_STATUS_SUCCESS) { + if (nss_status == NSS_STATUS_NOTFOUND) { + ret = KRB5_LNAME_NOTRANS; + } else { + ret = EIO; + } + goto done; + } + + if (pwd.pw_name == NULL) { + ret = EINVAL; + goto done; + } + + str = strdup(pwd.pw_name); + if (str == NULL) { + ret = ENOMEM; + goto done; + } + + *lname_out = str; + + ret = 0; + +done: + krb5_free_unparsed_name(context, princ_str); + free(buffer); + + return ret; +} + +static void sss_freestr(krb5_context context, + krb5_localauth_moddata data, char *str) +{ + free(str); +} + +krb5_error_code +localauth_sssd_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable) +{ + + if (maj_ver != 1 || min_ver != 1) { + return KRB5_PLUGIN_VER_NOTSUPP; + } + + krb5_localauth_vtable vt = (krb5_localauth_vtable)vtable; + + vt->init = NULL; + vt->fini = NULL; + vt->name = "sssd"; + vt->an2ln = sss_an2ln; + vt->userok = sss_userok; + vt->free_string = sss_freestr; + + return 0; +} + diff --git a/src/krb5_plugin/sssd_krb5_locator_plugin.c b/src/krb5_plugin/sssd_krb5_locator_plugin.c new file mode 100644 index 0000000..936815e --- /dev/null +++ b/src/krb5_plugin/sssd_krb5_locator_plugin.c @@ -0,0 +1,646 @@ +/* + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 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 "config.h" + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/types.h> +#include <netdb.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> + +#include "util/sss_krb5.h" +#include <krb5/locate_plugin.h> + +#include "providers/krb5/krb5_common.h" + +/* The following override of KDCINFO_TMPL and KPASSWDINFO_TMPL is not very + * elegant but since they are defined in krb5_common.h with the help of + * PUBCONF_PATH from config.h and PUBCONF_PATH can by set by a configure + * options I didn't found another way to change the path for a unit test. */ +#ifdef TEST_PUBCONF_PATH +#ifdef KDCINFO_TMPL +#undef KDCINFO_TMPL +#endif +#define KDCINFO_TMPL TEST_PUBCONF_PATH"/kdcinfo.%s" + +#ifdef KPASSWDINFO_TMPL +#undef KPASSWDINFO_TMPL +#endif +#define KPASSWDINFO_TMPL TEST_PUBCONF_PATH"/kpasswdinfo.%s" +#endif /* TEST_PUBCONF_PATH */ + +#define DEFAULT_KERBEROS_PORT 88 +#define DEFAULT_KADMIN_PORT 749 +#define DEFAULT_KPASSWD_PORT 464 + +#define BUFSIZE 4096 +#define PORT_STR_SIZE 7 +#define SSSD_KRB5_LOCATOR_DEBUG "SSSD_KRB5_LOCATOR_DEBUG" +#define SSSD_KRB5_LOCATOR_DISABLE "SSSD_KRB5_LOCATOR_DISABLE" +#define SSSD_KRB5_LOCATOR_IGNORE_DNS_FAILURES "SSSD_KRB5_LOCATOR_IGNORE_DNS_FAILURES" +#define DEBUG_KEY "[sssd_krb5_locator] " +#define PLUGIN_DEBUG(format, ...) do { \ + if (ctx->debug) { \ + plugin_debug_fn(format, ##__VA_ARGS__); \ + } \ +} while(0) + +struct addr_port { + char *addr; + uint16_t port; +}; + +struct sssd_ctx { + char *sssd_realm; + struct addr_port *kdc_addr; + struct addr_port *kpasswd_addr; + bool debug; + bool disabled; + bool kpasswdinfo_used; + bool ignore_dns_failure; +}; + +#ifdef HAVE_FUNCTION_ATTRIBUTE_FORMAT +__attribute__((format(printf, 1, 2))) +#endif +static void plugin_debug_fn(const char *format, ...) +{ + va_list ap; + char *s = NULL; + int ret; + + va_start(ap, format); + + ret = vasprintf(&s, format, ap); + va_end(ap); + if (ret < 0) { + /* ENOMEM */ + return; + } + + fprintf(stderr, DEBUG_KEY "%s", s); + free(s); +} + + +static void free_addr_port_list(struct addr_port **list) +{ + size_t c; + + if (list == NULL || *list == NULL) { + return; + } + + for (c = 0; (*list)[c].addr != NULL; c++) { + free((*list)[c].addr); + } + free(*list); + *list = NULL; +} + +static int copy_addr_port_list(struct addr_port *src, bool clear_port, + struct addr_port **dst) +{ + size_t c; + struct addr_port *d = NULL; + int ret; + + /* only copy if dst is initialized to NULL */ + if (dst == NULL || *dst != NULL) { + return EINVAL; + } + + if (src == NULL) { + return 0; + } + + for (c = 0; src[c].addr != NULL; c++); + + d = calloc((c + 1), sizeof(struct addr_port)); + if (d == NULL) { + return ENOMEM; + } + + for (c = 0; src[c].addr != NULL; c++) { + d[c].addr = strdup(src[c].addr); + if (d[c].addr == NULL) { + ret = ENOMEM; + goto done; + } + if (clear_port) { + d[c].port = 0; + } else { + d[c].port = src[c].port; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + free_addr_port_list(&d); + } else { + *dst = d; + } + + return ret; +} + +static int buf_to_addr_port_list(struct sssd_ctx *ctx, + uint8_t *buf, size_t buf_size, + struct addr_port **list) +{ + struct addr_port *l = NULL; + int ret; + uint8_t *p; + uint8_t *pn; + size_t c; + size_t len; + size_t addr_len; + char *addr_str = NULL; + char *tmp = NULL; + char *port_str; + long port; + char *endptr; + + /* only create if list is initialized to NULL */ + if (buf == NULL || buf_size == 0 || list == NULL || *list != NULL) { + return EINVAL; + } + + c = 1; /* to account for a missing \n at the very end */ + p = buf; + while ((p - buf) < buf_size + && (p = memchr(p, '\n', buf_size - (p - buf))) != NULL) { + p++; + c++; + } + + l = calloc((c + 1), sizeof(struct addr_port)); + if (l == NULL) { + return ENOMEM; + } + + c = 0; + p = buf; + do { + pn = memchr(p, '\n', buf_size - (p - buf)); + if (pn != NULL) { + len = pn - p; + } else { + len = buf_size - (p - buf); + } + if (len == 0) { + /* empty line no more processing */ + break; + } + + free(tmp); + tmp = strndup((char *) p, len); + if (tmp == NULL) { + ret = ENOMEM; + goto done; + } + + port_str = strrchr(tmp, ':'); + if (port_str == NULL) { + port = 0; + } else if (tmp[0] == '[' && *(port_str - 1) != ']') { + /* IPv6 address without port number */ + port = 0; + } else { + *port_str = '\0'; + ++port_str; + + if (isdigit(*port_str)) { + errno = 0; + port = strtol(port_str, &endptr, 10); + if (errno != 0) { + ret = errno; + PLUGIN_DEBUG("strtol failed on [%s]: [%d][%s], " + "assuming default.\n", + port_str, ret, strerror(ret)); + port = 0; + } + if (*endptr != '\0') { + PLUGIN_DEBUG("Found additional characters [%s] in port " + "number [%s], assuming default.\n", + endptr, port_str); + port = 0; + } + + if (port < 0 || port > 65535) { + PLUGIN_DEBUG("Illegal port number [%ld], assuming " + "default.\n", port); + port = 0; + } + } else { + PLUGIN_DEBUG("Illegal port number [%s], assuming default.\n", + port_str); + port = 0; + } + } + + /* make sure tmp is not modified so that it can be freed later */ + addr_str = tmp; + /* strip leading '[' and trailing ']' from IPv6 addresses */ + if (addr_str[0] == '[' + && (addr_len = strlen(addr_str)) + && addr_str[addr_len - 1] == ']') { + addr_str[addr_len -1] = '\0'; + addr_str++; + } + + PLUGIN_DEBUG("Found [%s][%ld].\n", addr_str, port); + + l[c].addr = strdup(addr_str); + if (l[c].addr == NULL) { + ret = ENOMEM; + goto done; + } + l[c].port = port; + + c++; + p = pn == NULL ? NULL : (pn + 1); + } while (p != NULL); + + ret = EOK; + +done: + free(tmp); + if (ret != EOK) { + free_addr_port_list(&l); + } else { + *list = l; + } + + return ret; +} + +static int get_krb5info(const char *realm, struct sssd_ctx *ctx, + enum locate_service_type svc) +{ + int ret; + char *krb5info_name = NULL; + size_t len; + uint8_t buf[BUFSIZE + 1]; + int fd = -1; + const char *name_tmpl = NULL; + + switch (svc) { + case locate_service_kdc: + name_tmpl = KDCINFO_TMPL; + break; + case locate_service_kpasswd: + name_tmpl = KPASSWDINFO_TMPL; + break; + default: + PLUGIN_DEBUG("Unsupported service [%d].\n", svc); + return EINVAL; + } + + + len = strlen(realm) + strlen(name_tmpl); + + krb5info_name = calloc(1, len + 1); + if (krb5info_name == NULL) { + PLUGIN_DEBUG("calloc failed.\n"); + return ENOMEM; + } + + ret = snprintf(krb5info_name, len, name_tmpl, realm); + if (ret < 0) { + PLUGIN_DEBUG("snprintf failed.\n"); + ret = EINVAL; + goto done; + } + krb5info_name[len] = '\0'; + + fd = open(krb5info_name, O_RDONLY); + if (fd == -1) { + ret = errno; + PLUGIN_DEBUG("open failed [%s][%d][%s].\n", + krb5info_name, ret, strerror(ret)); + goto done; + } + + memset(buf, 0, BUFSIZE+1); + + errno = 0; + len = sss_atomic_read_s(fd, buf, BUFSIZE); + if (len == -1) { + ret = errno; + PLUGIN_DEBUG("read failed [%d][%s].\n", ret, strerror(ret)); + close(fd); + goto done; + } + close(fd); + + if (len == BUFSIZE) { + PLUGIN_DEBUG("Content of krb5info file [%s] is [%d] or larger.\n", + krb5info_name, BUFSIZE); + } + + switch (svc) { + case locate_service_kdc: + free_addr_port_list(&(ctx->kdc_addr)); + ret = buf_to_addr_port_list(ctx, buf, len, &(ctx->kdc_addr)); + if (ret != EOK) { + goto done; + } + break; + case locate_service_kpasswd: + free_addr_port_list(&(ctx->kpasswd_addr)); + ret = buf_to_addr_port_list(ctx, buf, len, &(ctx->kpasswd_addr)); + if (ret != EOK) { + goto done; + } + break; + default: + PLUGIN_DEBUG("Unsupported service [%d].\n", svc); + ret = EINVAL; + goto done; + } + + ret = 0; +done: + free(krb5info_name); + return ret; +} + +krb5_error_code sssd_krb5_locator_init(krb5_context context, + void **private_data) +{ + struct sssd_ctx *ctx; + const char *dummy; + + ctx = calloc(1,sizeof(struct sssd_ctx)); + if (ctx == NULL) return KRB5_PLUGIN_NO_HANDLE; + + dummy = getenv(SSSD_KRB5_LOCATOR_DEBUG); + if (dummy == NULL) { + ctx->debug = false; + } else { + ctx->debug = true; + PLUGIN_DEBUG("sssd_krb5_locator_init called\n"); + } + + dummy = getenv(SSSD_KRB5_LOCATOR_DISABLE); + if (dummy == NULL) { + ctx->disabled = false; + } else { + ctx->disabled = true; + PLUGIN_DEBUG("SSSD KRB5 locator plugin is disabled.\n"); + } + + ctx->kpasswdinfo_used = false; + + dummy = getenv(SSSD_KRB5_LOCATOR_IGNORE_DNS_FAILURES); + if (dummy == NULL) { + ctx->ignore_dns_failure = false; + } else { + ctx->ignore_dns_failure = true; + PLUGIN_DEBUG("SSSD KRB5 locator plugin ignores DNS resolving errors.\n"); + } + + *private_data = ctx; + + return 0; +} + +void sssd_krb5_locator_close(void *private_data) +{ + struct sssd_ctx *ctx; + + if (private_data == NULL) return; + + ctx = (struct sssd_ctx *) private_data; + PLUGIN_DEBUG("sssd_krb5_locator_close called\n"); + + free_addr_port_list(&(ctx->kdc_addr)); + free_addr_port_list(&(ctx->kpasswd_addr)); + free(ctx->sssd_realm); + free(ctx); + + return; +} + +krb5_error_code sssd_krb5_locator_lookup(void *private_data, + enum locate_service_type svc, + const char *realm, + int socktype, + int family, + int (*cbfunc)(void *, int, struct sockaddr *), + void *cbdata) +{ + int ret; + struct addrinfo *ai, *ai_item; + struct sssd_ctx *ctx; + struct addrinfo ai_hints; + uint16_t port = 0; + uint16_t default_port = 0; + struct addr_port *addr = NULL; + char port_str[PORT_STR_SIZE]; + size_t c; + bool force_port = false; + char address[NI_MAXHOST]; + + if (private_data == NULL) return KRB5_PLUGIN_NO_HANDLE; + ctx = (struct sssd_ctx *) private_data; + + if (realm == NULL || cbfunc == NULL || cbdata == NULL) { + return KRB5_PLUGIN_NO_HANDLE; + } + + if (ctx->disabled) { + PLUGIN_DEBUG("Plugin disabled, nothing to do.\n"); + return KRB5_PLUGIN_NO_HANDLE; + } + + if (ctx->sssd_realm == NULL || strcmp(ctx->sssd_realm, realm) != 0) { + free(ctx->sssd_realm); + ctx->sssd_realm = strdup(realm); + if (ctx->sssd_realm == NULL) { + PLUGIN_DEBUG("strdup failed.\n"); + return KRB5_PLUGIN_NO_HANDLE; + } + + ret = get_krb5info(realm, ctx, locate_service_kdc); + if (ret != EOK) { + PLUGIN_DEBUG("get_krb5info failed.\n"); + return KRB5_PLUGIN_NO_HANDLE; + } + + } + + if (ctx->kpasswd_addr == NULL + && (svc == locate_service_kadmin || svc == locate_service_kpasswd || + svc == locate_service_master_kdc)) { + ret = get_krb5info(realm, ctx, locate_service_kpasswd); + if (ret != EOK) { + PLUGIN_DEBUG("reading kpasswd address failed, " + "using kdc address.\n"); + free_addr_port_list(&(ctx->kpasswd_addr)); + ret = copy_addr_port_list(ctx->kdc_addr, true, + &(ctx->kpasswd_addr)); + if (ret != EOK) { + PLUGIN_DEBUG("copying address list failed.\n"); + return KRB5_PLUGIN_NO_HANDLE; + } + } else { + ctx->kpasswdinfo_used = true; + } + } + + PLUGIN_DEBUG("sssd_realm[%s] requested realm[%s] family[%d] socktype[%d] " + "locate_service[%d]\n", + ctx->sssd_realm, realm, family, socktype, svc); + + switch (svc) { + case locate_service_kdc: + addr = ctx->kdc_addr; + default_port = DEFAULT_KERBEROS_PORT; + break; + case locate_service_master_kdc: + addr = ctx->kpasswd_addr; + default_port = DEFAULT_KERBEROS_PORT; + if (ctx->kpasswdinfo_used) { + /* Use default port if the addresses from the kpasswdinfo + * files are used because the port numbers from the file will + * most probably not be suitable. */ + force_port = true; + } + break; + case locate_service_kadmin: + addr = ctx->kpasswd_addr; + default_port = DEFAULT_KADMIN_PORT; + break; + case locate_service_kpasswd: + addr = ctx->kpasswd_addr; + default_port = DEFAULT_KPASSWD_PORT; + break; + case locate_service_krb524: + return KRB5_PLUGIN_NO_HANDLE; + default: + return KRB5_PLUGIN_NO_HANDLE; + } + + switch (family) { + case AF_UNSPEC: + case AF_INET: + case AF_INET6: + break; + default: + return KRB5_PLUGIN_NO_HANDLE; + } + + switch (socktype) { + case SOCK_STREAM: + case SOCK_DGRAM: + break; + default: + return KRB5_PLUGIN_NO_HANDLE; + } + + if (strcmp(realm, ctx->sssd_realm) != 0 || addr == NULL) { + return KRB5_PLUGIN_NO_HANDLE; + } + + for (c = 0; addr[c].addr != NULL; c++) { + port = ((addr[c].port == 0 || force_port) ? default_port + : addr[c].port); + memset(port_str, 0, PORT_STR_SIZE); + ret = snprintf(port_str, PORT_STR_SIZE-1, "%u", port); + if (ret < 0 || ret >= (PORT_STR_SIZE-1)) { + PLUGIN_DEBUG("snprintf failed.\n"); + return KRB5_PLUGIN_NO_HANDLE; + } + + memset(&ai_hints, 0, sizeof(struct addrinfo)); + ai_hints.ai_flags = AI_NUMERICSERV; + ai_hints.ai_socktype = socktype; + + ret = getaddrinfo(addr[c].addr, port_str, &ai_hints, &ai); + if (ret != 0) { + PLUGIN_DEBUG("getaddrinfo failed [%d][%s].\n", + ret, gai_strerror(ret)); + if (ret == EAI_SYSTEM) { + PLUGIN_DEBUG("getaddrinfo failed [%d][%s].\n", + errno, strerror(errno)); + } + + if (ctx->ignore_dns_failure) { + continue; + } + + return KRB5_PLUGIN_NO_HANDLE; + } + + for (ai_item = ai; ai_item != NULL; ai_item = ai_item->ai_next) { + if (ctx->debug) { + ret = getnameinfo(ai_item->ai_addr, ai_item->ai_addrlen, + address, NI_MAXHOST, + NULL, 0, + NI_NUMERICHOST); + if (ret != 0) { + address[0] = 0; + } + + PLUGIN_DEBUG("addr[%s (%s)] port[%s] family[%d] socktype[%d]\n", + addr[c].addr, address, + port_str, ai_item->ai_family, + ai_item->ai_socktype); + } + + if ((family == AF_UNSPEC || ai_item->ai_family == family) && + ai_item->ai_socktype == socktype) { + + ret = cbfunc(cbdata, socktype, ai_item->ai_addr); + if (ret != 0) { + PLUGIN_DEBUG("cbfunc failed\n"); + freeaddrinfo(ai); + return ret; + } else { + PLUGIN_DEBUG("[%s (%s)] used\n", addr[c].addr, address); + } + } else { + PLUGIN_DEBUG("[%s (%s)] NOT used\n", addr[c].addr, address); + } + } + freeaddrinfo(ai); + } + + return 0; +} + +const krb5plugin_service_locate_ftable service_locator = { + 0, /* version */ + sssd_krb5_locator_init, + sssd_krb5_locator_close, + sssd_krb5_locator_lookup, +}; |