>From 5e923a96b70c1124d393bb4f82beb3a5a4b6cc8f Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Wed, 11 Nov 2009 14:16:41 +0100 Subject: [PATCH] Validate Kerberos credentials with local keytab --- server/Makefile.am | 3 + server/config/SSSDConfig.py | 2 + server/config/SSSDConfigTest.py | 6 + server/config/etc/sssd.api.d/sssd-krb5.conf | 4 +- server/external/krb5.m4 | 3 +- server/man/sssd-ipa.5.xml | 17 +++ server/man/sssd-krb5.5.xml | 25 +++++ server/providers/ipa/ipa_common.c | 2 + server/providers/krb5/krb5_auth.c | 74 +++++++------- server/providers/krb5/krb5_become_user.c | 61 +++++++++++ server/providers/krb5/krb5_child.c | 148 ++++++++++++++++++++++++++- server/providers/krb5/krb5_common.c | 2 + server/providers/krb5/krb5_common.h | 2 + server/providers/krb5/krb5_utils.h | 2 + server/util/sss_krb5.c | 16 +++- server/util/sss_krb5.h | 2 + 16 files changed, 326 insertions(+), 43 deletions(-) create mode 100644 server/providers/krb5/krb5_become_user.c diff --git a/server/Makefile.am b/server/Makefile.am index 08c0295..6dfc2ae 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -564,6 +564,7 @@ libsss_proxy_la_LDFLAGS = \ libsss_krb5_la_SOURCES = \ providers/krb5/krb5_utils.c \ + providers/krb5/krb5_become_user.c \ providers/krb5/krb5_auth.c \ providers/krb5/krb5_common.c \ providers/krb5/krb5_init.c @@ -591,6 +592,7 @@ libsss_ipa_la_SOURCES = \ util/sss_ldap.c \ util/sss_krb5.c \ providers/krb5/krb5_utils.c \ + providers/krb5/krb5_become_user.c \ providers/krb5/krb5_common.c \ providers/krb5/krb5_auth.c libsss_ipa_la_CFLAGS = \ @@ -606,6 +608,7 @@ libsss_ipa_la_LDFLAGS = \ krb5_child_SOURCES = \ $(SSSD_DEBUG_OBJ) \ + providers/krb5/krb5_become_user.c \ providers/krb5/krb5_child.c \ util/sss_krb5.c krb5_child_CFLAGS = \ diff --git a/server/config/SSSDConfig.py b/server/config/SSSDConfig.py index 162354b..77222ae 100644 --- a/server/config/SSSDConfig.py +++ b/server/config/SSSDConfig.py @@ -90,6 +90,8 @@ option_strings = { # [provider/krb5/auth] 'krb5_ccachedir' : _('Directory to store credential caches'), 'krb5_ccname_template' : _("Location of the user's credential cache"), + 'krb5_keytab' : _("Location of the keytab to validate credentials"), + 'krb5_validate' : _("Enable credential validation"), # [provider/krb5/chpass] 'krb5_changepw_principal' : _('The principal of the change password service'), diff --git a/server/config/SSSDConfigTest.py b/server/config/SSSDConfigTest.py index 944f86f..56b6ef4 100644 --- a/server/config/SSSDConfigTest.py +++ b/server/config/SSSDConfigTest.py @@ -479,6 +479,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'krb5_realm', 'krb5_ccachedir', 'krb5_ccname_template', + 'krb5_keytab', + 'krb5_validate', 'krb5_auth_timeout']) options = domain.list_options() @@ -550,6 +552,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'krb5_realm', 'krb5_ccachedir', 'krb5_ccname_template', + 'krb5_keytab', + 'krb5_validate', 'krb5_auth_timeout'] self.assertTrue(type(options) == dict, @@ -698,6 +702,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'krb5_realm', 'krb5_ccachedir', 'krb5_ccname_template', + 'krb5_keytab', + 'krb5_validate', 'krb5_auth_timeout']) options = domain.list_options() diff --git a/server/config/etc/sssd.api.d/sssd-krb5.conf b/server/config/etc/sssd.api.d/sssd-krb5.conf index 85067e9..860f8b8 100644 --- a/server/config/etc/sssd.api.d/sssd-krb5.conf +++ b/server/config/etc/sssd.api.d/sssd-krb5.conf @@ -6,8 +6,10 @@ krb5_auth_timeout = int, None [provider/krb5/auth] krb5_ccachedir = str, None krb5_ccname_template = str, None +krb5_keytab = str, None +krb5_validate = bool, None [provider/krb5/access] [provider/krb5/chpass] -krb5_changepw_principal = str, None \ No newline at end of file +krb5_changepw_principal = str, None diff --git a/server/external/krb5.m4 b/server/external/krb5.m4 index d02732b..84bb441 100644 --- a/server/external/krb5.m4 +++ b/server/external/krb5.m4 @@ -37,7 +37,8 @@ SAVE_LIBS=$LIBS CFLAGS="$CFLAGS $KRB5_CFLAGS" LIBS="$LIBS $KRB5_LIBS" AC_CHECK_HEADERS([krb5.h krb5/krb5.h]) -AC_CHECK_FUNCS([krb5_get_init_creds_opt_alloc krb5_get_error_message]) +AC_CHECK_FUNCS([krb5_get_init_creds_opt_alloc krb5_get_error_message \ + krb5_free_unparsed_name]) CFLAGS=$SAVE_CFLAGS LIBS=$SAVE_LIBS diff --git a/server/man/sssd-ipa.5.xml b/server/man/sssd-ipa.5.xml index 31ce824..2751591 100644 --- a/server/man/sssd-ipa.5.xml +++ b/server/man/sssd-ipa.5.xml @@ -94,6 +94,23 @@ + + krb5_validate (boolean) + + + Verify with the help of krb5_keytab that the TGT + obtained has not been spoofed. + + + Default: true + + + Please note that this default differs from the + traditional kerberos provider backend. + + + + diff --git a/server/man/sssd-krb5.5.xml b/server/man/sssd-krb5.5.xml index 1f86b49..1ca2831 100644 --- a/server/man/sssd-krb5.5.xml +++ b/server/man/sssd-krb5.5.xml @@ -178,6 +178,31 @@ + + krb5_validate (boolean) + + + Verify with the help of krb5_keytab that the TGT obtained has not been spoofed. + + + Default: false + + + + + + krb5_keytab (string) + + + The location of the keytab to use when validating + credentials obtained from KDCs. + + + Default: /etc/krb5.keytab + + + + diff --git a/server/providers/ipa/ipa_common.c b/server/providers/ipa/ipa_common.c index 54044b9..38e4d53 100644 --- a/server/providers/ipa/ipa_common.c +++ b/server/providers/ipa/ipa_common.c @@ -112,6 +112,8 @@ struct dp_option ipa_def_krb5_opts[] = { { "krb5_ccname_template", DP_OPT_STRING, { "FILE:%d/krb5cc_%U_XXXXXX" }, NULL_STRING}, { "krb5_changepw_principal", DP_OPT_STRING, { "kadmin/changepw" }, NULL_STRING }, { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER }, + { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING }, + { "krb5_validate", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE } }; int domain_to_basedn(TALLOC_CTX *memctx, const char *domain, char **basedn) diff --git a/server/providers/krb5/krb5_auth.c b/server/providers/krb5/krb5_auth.c index a02147e..d3e05e1 100644 --- a/server/providers/krb5/krb5_auth.c +++ b/server/providers/krb5/krb5_auth.c @@ -43,36 +43,6 @@ #define KRB5_CHILD SSSD_LIBEXEC_PATH"/krb5_child" #endif -static errno_t become_user(uid_t uid, gid_t gid) -{ - int ret; - ret = setgid(gid); - if (ret == -1) { - DEBUG(1, ("setgid failed [%d][%s].\n", errno, strerror(errno))); - return errno; - } - - ret = setuid(uid); - if (ret == -1) { - DEBUG(1, ("setuid failed [%d][%s].\n", errno, strerror(errno))); - return errno; - } - - ret = setegid(gid); - if (ret == -1) { - DEBUG(1, ("setegid failed [%d][%s].\n", errno, strerror(errno))); - return errno; - } - - ret = seteuid(uid); - if (ret == -1) { - DEBUG(1, ("seteuid failed [%d][%s].\n", errno, strerror(errno))); - return errno; - } - - return EOK; -} - struct io_buffer { uint8_t *data; size_t size; @@ -82,6 +52,16 @@ errno_t create_send_buffer(struct krb5child_req *kr, struct io_buffer **io_buf) { struct io_buffer *buf; size_t rp; + const char *keytab; + uint32_t validate; + + keytab = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_KEYTAB); + if (keytab == NULL) { + DEBUG(1, ("Missing keytab option.\n")); + return EINVAL; + } + + validate = dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ? 1 : 0; buf = talloc(kr, struct io_buffer); if (buf == NULL) { @@ -89,10 +69,11 @@ errno_t create_send_buffer(struct krb5child_req *kr, struct io_buffer **io_buf) return ENOMEM; } - buf->size = 4*sizeof(int) + strlen(kr->pd->upn) + strlen(kr->ccname) + + buf->size = 8*sizeof(uint32_t) + strlen(kr->pd->upn) + strlen(kr->ccname) + + strlen(keytab) + kr->pd->authtok_size; if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) { - buf->size += sizeof(int) + kr->pd->newauthtok_size; + buf->size += sizeof(uint32_t) + kr->pd->newauthtok_size; } buf->data = talloc_size(kr, buf->size); @@ -106,6 +87,15 @@ errno_t create_send_buffer(struct krb5child_req *kr, struct io_buffer **io_buf) ((uint32_t *)(&buf->data[rp]))[0] = kr->pd->cmd; rp += sizeof(uint32_t); + ((uint32_t *)(&buf->data[rp]))[0] = kr->pd->pw_uid; + rp += sizeof(uint32_t); + + ((uint32_t *)(&buf->data[rp]))[0] = kr->pd->gr_gid; + rp += sizeof(uint32_t); + + ((uint32_t *)(&buf->data[rp]))[0] = validate; + rp += sizeof(uint32_t); + ((uint32_t *)(&buf->data[rp]))[0] = (uint32_t) strlen(kr->pd->upn); rp += sizeof(uint32_t); @@ -118,6 +108,12 @@ errno_t create_send_buffer(struct krb5child_req *kr, struct io_buffer **io_buf) memcpy(&buf->data[rp], kr->ccname, strlen(kr->ccname)); rp += strlen(kr->ccname); + ((uint32_t *)(&buf->data[rp]))[0] = (uint32_t) strlen(keytab); + rp += sizeof(uint32_t); + + memcpy(&buf->data[rp], keytab, strlen(keytab)); + rp += strlen(keytab); + ((uint32_t *)(&buf->data[rp]))[0] = kr->pd->authtok_size; rp += sizeof(uint32_t); @@ -436,13 +432,17 @@ static errno_t fork_child(struct krb5child_req *kr) return err; } - ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid); - if (ret != EOK) { - DEBUG(1, ("become_user failed.\n")); - return ret; + /* We need to keep the root privileges to read the keytab file if + * validation is enabled, otherwise we can drop them here and run + * krb5_child with user privileges. */ + if (!dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE)) { + ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid); + if (ret != EOK) { + DEBUG(1, ("become_user failed.\n")); + return ret; + } } - close(pipefd_to_child[1]); ret = dup2(pipefd_to_child[0],STDIN_FILENO); if (ret == -1) { diff --git a/server/providers/krb5/krb5_become_user.c b/server/providers/krb5/krb5_become_user.c new file mode 100644 index 0000000..351f539 --- /dev/null +++ b/server/providers/krb5/krb5_become_user.c @@ -0,0 +1,61 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Utilities + + Authors: + Sumit Bose + + 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 . +*/ + +#include +#include + +#include "util/util.h" + +errno_t become_user(uid_t uid, gid_t gid) +{ + int ret; + + DEBUG(9, ("Trying to become user [%d][%d].\n", uid, gid)); + ret = setgid(gid); + if (ret == -1) { + DEBUG(1, ("setgid failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + ret = setuid(uid); + if (ret == -1) { + DEBUG(1, ("setuid failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + ret = setegid(gid); + if (ret == -1) { + DEBUG(1, ("setegid failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + ret = seteuid(uid); + if (ret == -1) { + DEBUG(1, ("seteuid failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + return EOK; +} + diff --git a/server/providers/krb5/krb5_child.c b/server/providers/krb5/krb5_child.c index 2630e0f..66fccf8 100644 --- a/server/providers/krb5/krb5_child.c +++ b/server/providers/krb5/krb5_child.c @@ -88,6 +88,8 @@ struct krb5_req { errno_t (*child_req)(int fd, struct krb5_req *kr); char *ccname; + char *keytab; + bool validate; }; static krb5_context krb5_error_ctx; @@ -193,10 +195,104 @@ static struct response *prepare_response_message(struct krb5_req *kr, return resp; } +static krb5_error_code validate_tgt(struct krb5_req *kr) +{ + krb5_error_code kerr; + krb5_error_code kt_err; + char *principal; + krb5_keytab keytab; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_verify_init_creds_opt opt; + + memset(&keytab, 0, sizeof(keytab)); + kerr = krb5_kt_resolve(kr->ctx, kr->keytab, &keytab); + if (kerr != 0) { + DEBUG(1, ("error resolving keytab [%s], not verifying TGT.\n", + kr->keytab)); + return kerr; + } + + memset(&cursor, 0, sizeof(cursor)); + kerr = krb5_kt_start_seq_get(kr->ctx, keytab, &cursor); + if (kerr != 0) { + DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n", + kr->keytab)); + return kerr; + } + + /* We look for the first entry from our realm or take the last one */ + memset(&entry, 0, sizeof(entry)); + while ((kt_err = krb5_kt_next_entry(kr->ctx, keytab, &entry, &cursor)) == 0) { + if (krb5_realm_compare(kr->ctx, entry.principal, kr->princ)) { + DEBUG(9, ("Found keytab entry with the realm of the credential.\n")); + break; + } + + kerr = krb5_free_keytab_entry_contents(kr->ctx, &entry); + if (kerr != 0) { + DEBUG(1, ("Failed to free keytab entry.\n")); + } + memset(&entry, 0, sizeof(entry)); + } + + /* Close the keytab here. Even though we're using cursors, the file + * handle is stored in the krb5_keytab structure, and it gets + * overwritten when the verify_init_creds() call below creates its own + * cursor, creating a leak. */ + kerr = krb5_kt_end_seq_get(kr->ctx, keytab, &cursor); + if (kerr != 0) { + DEBUG(1, ("krb5_kt_end_seq_get failed, not verifying TGT.\n")); + goto done; + } + + /* check if we got any errors from krb5_kt_next_entry */ + if (kt_err != 0 && kt_err != KRB5_KT_END) { + DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n", + kr->keytab)); + goto done; + } + + /* Get the principal to which the key belongs, for logging purposes. */ + principal = NULL; + kerr = krb5_unparse_name(kr->ctx, entry.principal, &principal); + if (kerr != 0) { + DEBUG(1, ("internal error parsing principal name, " + "not verifying TGT.\n")); + goto done; + } + + + krb5_verify_init_creds_opt_init(&opt); + kerr = krb5_verify_init_creds(kr->ctx, kr->creds, entry.principal, keytab, + NULL, &opt); + + if (kerr == 0) { + DEBUG(5, ("TGT verified using key for [%s].\n", principal)); + } else { + DEBUG(1 ,("TGT failed verification using key for [%s].\n", principal)); + } + +done: + if (krb5_kt_close(kr->ctx, keytab) != 0) { + DEBUG(1, ("krb5_kt_close failed")); + } + if (krb5_free_keytab_entry_contents(kr->ctx, &entry) != 0) { + DEBUG(1, ("Failed to free keytab entry.\n")); + } + if (principal != NULL) { + sss_krb5_free_unparsed_name(kr->ctx, principal); + } + + return kerr; + +} + static krb5_error_code get_and_save_tgt(struct krb5_req *kr, char *password) { krb5_error_code kerr = 0; + int ret; int fd = -1; size_t ccname_len = 0; size_t offset = 0; @@ -209,6 +305,25 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr, return kerr; } + if (kr->validate) { + kerr = validate_tgt(kr); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + return kerr; + } + + /* We drop root privileges which were needed to read the keytab file + * for the validation validation of the credentials here to run the + * ccache I/O operations with user privileges. */ + ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid); + if (ret != EOK) { + DEBUG(1, ("become_user failed.\n")); + return ret; + } + } else { + DEBUG(9, ("TGT validation is disabled.\n")); + } + if (kr->ccname[0] == '/' || strncmp(kr->ccname, "FILE:", 5) == 0) { offset = 0; if (kr->ccname[0] == 'F') { @@ -467,7 +582,7 @@ uint8_t *copy_buffer_and_add_zero(TALLOC_CTX *mem_ctx, const uint8_t *src, size_ } static errno_t unpack_buffer(uint8_t *buf, size_t size, struct pam_data *pd, - char **ccname) + char **ccname, char **keytab, uint32_t *validate) { size_t p = 0; uint32_t *len; @@ -479,6 +594,21 @@ static errno_t unpack_buffer(uint8_t *buf, size_t size, struct pam_data *pd, if ((p + sizeof(uint32_t)) > size) return EINVAL; len = ((uint32_t *)(buf+p)); + pd->pw_uid = *len; + p += sizeof(uint32_t); + + if ((p + sizeof(uint32_t)) > size) return EINVAL; + len = ((uint32_t *)(buf+p)); + pd->gr_gid = *len; + p += sizeof(uint32_t); + + if ((p + sizeof(uint32_t)) > size) return EINVAL; + len = ((uint32_t *)(buf+p)); + *validate = *len; + p += sizeof(uint32_t); + + if ((p + sizeof(uint32_t)) > size) return EINVAL; + len = ((uint32_t *)(buf+p)); p += sizeof(uint32_t); if ((p + *len ) > size) return EINVAL; @@ -501,6 +631,16 @@ static errno_t unpack_buffer(uint8_t *buf, size_t size, struct pam_data *pd, len = ((uint32_t *)(buf+p)); p += sizeof(uint32_t); + if ((p + *len ) > size) return EINVAL; + *keytab = (char *) copy_buffer_and_add_zero(pd, buf+p, + sizeof(char) * (*len)); + if (*keytab == NULL) return ENOMEM; + p += *len; + + if ((p + sizeof(uint32_t)) > size) return EINVAL; + len = ((uint32_t *)(buf+p)); + p += sizeof(uint32_t); + if ((p + *len) > size) return EINVAL; pd->authtok = copy_buffer_and_add_zero(pd, buf+p, sizeof(char) * (*len)); if (pd->authtok == NULL) return ENOMEM; @@ -667,6 +807,8 @@ int main(int argc, const char *argv[]) struct pam_data *pd = NULL; struct krb5_req *kr = NULL; char *ccname; + char *keytab; + uint32_t validate; int opt; poptContext pc; int debug_fd = -1; @@ -738,7 +880,7 @@ int main(int argc, const char *argv[]) } close(STDIN_FILENO); - ret = unpack_buffer(buf, len, pd, &ccname); + ret = unpack_buffer(buf, len, pd, &ccname, &keytab, &validate); if (ret != EOK) { DEBUG(1, ("unpack_buffer failed.\n")); goto fail; @@ -750,6 +892,8 @@ int main(int argc, const char *argv[]) goto fail; } kr->ccname = ccname; + kr->keytab = keytab; + kr->validate = (validate == 0) ? false : true; ret = kr->child_req(STDOUT_FILENO, kr); if (ret != EOK) { diff --git a/server/providers/krb5/krb5_common.c b/server/providers/krb5/krb5_common.c index de069cd..30878de 100644 --- a/server/providers/krb5/krb5_common.c +++ b/server/providers/krb5/krb5_common.c @@ -35,6 +35,8 @@ struct dp_option default_krb5_opts[] = { { "krb5_ccname_template", DP_OPT_STRING, { "FILE:%d/krb5cc_%U_XXXXXX" }, NULL_STRING}, { "krb5_changepw_principal", DP_OPT_STRING, { "kadmin/changepw" }, NULL_STRING }, { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER }, + { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING }, + { "krb5_validate", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE } }; errno_t check_and_export_options(struct dp_option *opts, diff --git a/server/providers/krb5/krb5_common.h b/server/providers/krb5/krb5_common.h index 60f6a82..cb60f42 100644 --- a/server/providers/krb5/krb5_common.h +++ b/server/providers/krb5/krb5_common.h @@ -44,6 +44,8 @@ enum krb5_opts { KRB5_CCNAME_TMPL, KRB5_CHANGEPW_PRINC, KRB5_AUTH_TIMEOUT, + KRB5_KEYTAB, + KRB5_VALIDATE, KRB5_OPTS }; diff --git a/server/providers/krb5/krb5_utils.h b/server/providers/krb5/krb5_utils.h index dec6f7f..7637041 100644 --- a/server/providers/krb5/krb5_utils.h +++ b/server/providers/krb5/krb5_utils.h @@ -34,4 +34,6 @@ char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr, const char *template); +errno_t become_user(uid_t uid, gid_t gid); + #endif /* __KRB5_UTILS_H__ */ diff --git a/server/util/sss_krb5.c b/server/util/sss_krb5.c index 59e278e..e96e1ba 100644 --- a/server/util/sss_krb5.c +++ b/server/util/sss_krb5.c @@ -83,10 +83,22 @@ void KRB5_CALLCONV sss_krb5_get_init_creds_opt_free (krb5_context context, krb5_get_init_creds_opt *opt) { #ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC - krb5_get_init_creds_opt_free(context, opt); + krb5_get_init_creds_opt_free(context, opt); #else - free(opt); + free(opt); #endif return; } + +void KRB5_CALLCONV sss_krb5_free_unparsed_name(krb5_context context, char *name) +{ +#ifdef HAVE_KRB5_FREE_UNPARSED_NAME + krb5_free_unparsed_name(context, name); +#else + if (name != NULL) { + memset(name, 0, strlen(name)); + free(name); + } +#endif +} diff --git a/server/util/sss_krb5.h b/server/util/sss_krb5.h index 755cf81..342196d 100644 --- a/server/util/sss_krb5.h +++ b/server/util/sss_krb5.h @@ -42,4 +42,6 @@ krb5_error_code KRB5_CALLCONV sss_krb5_get_init_creds_opt_alloc( void KRB5_CALLCONV sss_krb5_get_init_creds_opt_free (krb5_context context, krb5_get_init_creds_opt *opt); + +void KRB5_CALLCONV sss_krb5_free_unparsed_name(krb5_context context, char *name); #endif /* __SSS_KRB5_H__ */ -- 1.6.2.5