s4-loadparm: 2nd half of lp_ to lpcfg_ conversion
[sfrench/samba-autobuild/.git] / source4 / auth / credentials / credentials_krb5.c
index 52bf9f124feee904c59c21c5299d01544edcd196..622f674bf841c25c6ed0606106b2bb7284f43fc4 100644 (file)
 #include "system/kerberos.h"
 #include "auth/kerberos/kerberos.h"
 #include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_proto.h"
 #include "auth/credentials/credentials_krb5.h"
+#include "auth/kerberos/kerberos_credentials.h"
 #include "param/param.h"
 
-int cli_credentials_get_krb5_context(struct cli_credentials *cred, 
+_PUBLIC_ int cli_credentials_get_krb5_context(struct cli_credentials *cred, 
+                                             struct tevent_context *event_ctx,
                                     struct loadparm_context *lp_ctx,
                                     struct smb_krb5_context **smb_krb5_context) 
 {
@@ -38,8 +41,8 @@ int cli_credentials_get_krb5_context(struct cli_credentials *cred,
                return 0;
        }
 
-       ret = smb_krb5_init_context(cred, cli_credentials_get_event_context(cred), 
-                                   lp_ctx, &cred->smb_krb5_context);
+       ret = smb_krb5_init_context(cred, event_ctx, lp_ctx,
+                                   &cred->smb_krb5_context);
        if (ret) {
                cred->smb_krb5_context = NULL;
                return ret;
@@ -52,7 +55,7 @@ int cli_credentials_get_krb5_context(struct cli_credentials *cred,
  * otherwise we might have problems with the krb5 context already
  * being here.
  */
-NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred, 
+_PUBLIC_ NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred, 
                                          struct smb_krb5_context *smb_krb5_context)
 {
        if (!talloc_reference(cred, smb_krb5_context)) {
@@ -63,14 +66,14 @@ NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred,
 }
 
 static int cli_credentials_set_from_ccache(struct cli_credentials *cred, 
-                                   struct ccache_container *ccache,
-                                   enum credentials_obtained obtained)
+                                          struct ccache_container *ccache,
+                                          enum credentials_obtained obtained,
+                                          const char **error_string)
 {
        
        krb5_principal princ;
        krb5_error_code ret;
        char *name;
-       char **realm;
 
        if (cred->ccache_obtained > obtained) {
                return 0;
@@ -80,25 +83,20 @@ static int cli_credentials_set_from_ccache(struct cli_credentials *cred,
                                    ccache->ccache, &princ);
 
        if (ret) {
-               char *err_mess = smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context, 
-                                                           ret, cred);
-               DEBUG(1,("failed to get principal from ccache: %s\n", 
-                        err_mess));
-               talloc_free(err_mess);
+               (*error_string) = talloc_asprintf(cred, "failed to get principal from ccache: %s\n",
+                                                 smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context,
+                                                                            ret, cred));
                return ret;
        }
        
        ret = krb5_unparse_name(ccache->smb_krb5_context->krb5_context, princ, &name);
        if (ret) {
-               char *err_mess = smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context, ret, cred);
-               DEBUG(1,("failed to unparse principal from ccache: %s\n", 
-                        err_mess));
-               talloc_free(err_mess);
+               (*error_string) = talloc_asprintf(cred, "failed to unparse principal from ccache: %s\n",
+                                                 smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context,
+                                                                            ret, cred));
                return ret;
        }
 
-       realm = krb5_princ_realm(ccache->smb_krb5_context->krb5_context, princ);
-
        cli_credentials_set_principal(cred, name, obtained);
 
        free(name);
@@ -126,10 +124,12 @@ static int free_dccache(struct ccache_container *ccc) {
        return 0;
 }
 
-int cli_credentials_set_ccache(struct cli_credentials *cred, 
-                              struct loadparm_context *lp_ctx,
-                              const char *name, 
-                              enum credentials_obtained obtained)
+_PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred, 
+                                       struct tevent_context *event_ctx,
+                                       struct loadparm_context *lp_ctx,
+                                       const char *name,
+                                       enum credentials_obtained obtained,
+                                       const char **error_string)
 {
        krb5_error_code ret;
        krb5_principal princ;
@@ -140,34 +140,39 @@ int cli_credentials_set_ccache(struct cli_credentials *cred,
 
        ccc = talloc(cred, struct ccache_container);
        if (!ccc) {
+               (*error_string) = error_message(ENOMEM);
                return ENOMEM;
        }
 
-       ret = cli_credentials_get_krb5_context(cred, lp_ctx, 
+       ret = cli_credentials_get_krb5_context(cred, event_ctx, lp_ctx,
                                               &ccc->smb_krb5_context);
        if (ret) {
+               (*error_string) = error_message(ret);
                talloc_free(ccc);
                return ret;
        }
        if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
                talloc_free(ccc);
+               (*error_string) = error_message(ENOMEM);
                return ENOMEM;
        }
 
        if (name) {
                ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, name, &ccc->ccache);
                if (ret) {
-                       DEBUG(1,("failed to read krb5 ccache: %s: %s\n", 
-                                name, 
-                                smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
+                       (*error_string) = talloc_asprintf(cred, "failed to read krb5 ccache: %s: %s\n",
+                                                         name,
+                                                         smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
+                                                                                    ret, ccc));
                        talloc_free(ccc);
                        return ret;
                }
        } else {
                ret = krb5_cc_default(ccc->smb_krb5_context->krb5_context, &ccc->ccache);
                if (ret) {
-                       DEBUG(3,("failed to read default krb5 ccache: %s\n", 
-                                smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
+                       (*error_string) = talloc_asprintf(cred, "failed to read default krb5 ccache: %s\n",
+                                                         smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
+                                                                                    ret, ccc));
                        talloc_free(ccc);
                        return ret;
                }
@@ -177,85 +182,102 @@ int cli_credentials_set_ccache(struct cli_credentials *cred,
 
        ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, ccc->ccache, &princ);
 
-       if (ret) {
-               DEBUG(3,("failed to get principal from default ccache: %s\n", 
-                        smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
-               talloc_free(ccc);               
-               return ret;
-       }
+       if (ret == 0) {
+               krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ);
+               ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
 
-       krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ);
+               if (ret) {
+                       (*error_string) = error_message(ret);
+                       return ret;
+               }
 
-       ret = cli_credentials_set_from_ccache(cred, ccc, obtained);
+               cred->ccache = ccc;
+               cred->ccache_obtained = obtained;
+               talloc_steal(cred, ccc);
 
-       if (ret) {
-               return ret;
+               cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
+               return 0;
        }
-
-       cred->ccache = ccc;
-       cred->ccache_obtained = obtained;
-       talloc_steal(cred, ccc);
-
-       cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
        return 0;
 }
 
 
 static int cli_credentials_new_ccache(struct cli_credentials *cred, 
+                                     struct tevent_context *event_ctx,
                                      struct loadparm_context *lp_ctx,
-                                     struct ccache_container **_ccc)
+                                     char *ccache_name,
+                                     struct ccache_container **_ccc,
+                                     const char **error_string)
 {
+       bool must_free_cc_name = false;
        krb5_error_code ret;
        struct ccache_container *ccc = talloc(cred, struct ccache_container);
-       char *ccache_name;
        if (!ccc) {
                return ENOMEM;
        }
 
-       ccache_name = talloc_asprintf(ccc, "MEMORY:%p", 
-                                     ccc);
-
-       if (!ccache_name) {
-               talloc_free(ccc);
-               return ENOMEM;
-       }
-
-       ret = cli_credentials_get_krb5_context(cred, lp_ctx, 
+       ret = cli_credentials_get_krb5_context(cred, event_ctx, lp_ctx,
                                               &ccc->smb_krb5_context);
        if (ret) {
                talloc_free(ccc);
+               (*error_string) = talloc_asprintf(cred, "Failed to get krb5_context: %s",
+                                                 error_message(ret));
                return ret;
        }
        if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
                talloc_free(ccc);
+               (*error_string) = strerror(ENOMEM);
                return ENOMEM;
        }
 
+       if (!ccache_name) {
+               must_free_cc_name = true;
+               ccache_name = talloc_asprintf(ccc, "MEMORY:%p", 
+                                             ccc);
+               
+               if (!ccache_name) {
+                       talloc_free(ccc);
+                       (*error_string) = strerror(ENOMEM);
+                       return ENOMEM;
+               }
+       }
+
        ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name, 
                              &ccc->ccache);
        if (ret) {
-               DEBUG(1,("failed to generate a new krb5 ccache (%s): %s\n", 
-                        ccache_name,
-                        smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
+               (*error_string) = talloc_asprintf(cred, "failed to resolve a krb5 ccache (%s): %s\n",
+                                                 ccache_name,
+                                                 smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
+                                                                            ret, ccc));
                talloc_free(ccache_name);
                talloc_free(ccc);
                return ret;
        }
 
-       talloc_set_destructor(ccc, free_mccache);
+       if (strncasecmp(ccache_name, "MEMORY:", 7) == 0) {
+               talloc_set_destructor(ccc, free_mccache);
+       } else {
+               talloc_set_destructor(ccc, free_dccache);
+       }
 
-       talloc_free(ccache_name);
+       if (must_free_cc_name) {
+               talloc_free(ccache_name);
+       }
 
        *_ccc = ccc;
 
-       return ret;
+       return 0;
 }
 
-int cli_credentials_get_ccache(struct cli_credentials *cred, 
-                              struct loadparm_context *lp_ctx,
-                              struct ccache_container **ccc)
+_PUBLIC_ int cli_credentials_get_named_ccache(struct cli_credentials *cred, 
+                                             struct tevent_context *event_ctx,
+                                             struct loadparm_context *lp_ctx,
+                                             char *ccache_name,
+                                             struct ccache_container **ccc,
+                                             const char **error_string)
 {
        krb5_error_code ret;
+       enum credentials_obtained obtained;
        
        if (cred->machine_account_pending) {
                cli_credentials_set_machine_account(cred, lp_ctx);
@@ -267,23 +289,22 @@ int cli_credentials_get_ccache(struct cli_credentials *cred,
                return 0;
        }
        if (cli_credentials_is_anonymous(cred)) {
+               (*error_string) = "Cannot get anonymous kerberos credentials";
                return EINVAL;
        }
 
-       ret = cli_credentials_new_ccache(cred, lp_ctx, ccc);
+       ret = cli_credentials_new_ccache(cred, event_ctx, lp_ctx, ccache_name, ccc, error_string);
        if (ret) {
                return ret;
        }
 
-       ret = kinit_to_ccache(cred, cred, (*ccc)->smb_krb5_context, (*ccc)->ccache);
+       ret = kinit_to_ccache(cred, cred, (*ccc)->smb_krb5_context, (*ccc)->ccache, &obtained, error_string);
        if (ret) {
                return ret;
        }
 
        ret = cli_credentials_set_from_ccache(cred, *ccc, 
-                                             (MAX(MAX(cred->principal_obtained, 
-                                                      cred->username_obtained), 
-                                                  cred->password_obtained)));
+                                             obtained, error_string);
        
        cred->ccache = *ccc;
        cred->ccache_obtained = cred->principal_obtained;
@@ -291,7 +312,26 @@ int cli_credentials_get_ccache(struct cli_credentials *cred,
                return ret;
        }
        cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
-       return ret;
+       return 0;
+}
+
+_PUBLIC_ int cli_credentials_get_ccache(struct cli_credentials *cred, 
+                                       struct tevent_context *event_ctx,
+                                       struct loadparm_context *lp_ctx,
+                                       struct ccache_container **ccc,
+                                       const char **error_string)
+{
+       return cli_credentials_get_named_ccache(cred, event_ctx, lp_ctx, NULL, ccc, error_string);
+}
+
+/* We have good reason to think the ccache in these credentials is invalid - blow it away */
+static void cli_credentials_unconditionally_invalidate_client_gss_creds(struct cli_credentials *cred)
+{
+       if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
+               talloc_unlink(cred, cred->client_gss_creds);
+               cred->client_gss_creds = NULL;
+       }
+       cred->client_gss_creds_obtained = CRED_UNINITIALISED;
 }
 
 void cli_credentials_invalidate_client_gss_creds(struct cli_credentials *cred, 
@@ -315,7 +355,19 @@ void cli_credentials_invalidate_client_gss_creds(struct cli_credentials *cred,
        }
 }
 
-void cli_credentials_invalidate_ccache(struct cli_credentials *cred, 
+/* We have good reason to think this CCACHE is invalid.  Blow it away */
+static void cli_credentials_unconditionally_invalidate_ccache(struct cli_credentials *cred)
+{
+       if (cred->ccache_obtained > CRED_UNINITIALISED) {
+               talloc_unlink(cred, cred->ccache);
+               cred->ccache = NULL;
+       }
+       cred->ccache_obtained = CRED_UNINITIALISED;
+
+       cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
+}
+
+_PUBLIC_ void cli_credentials_invalidate_ccache(struct cli_credentials *cred, 
                                       enum credentials_obtained obtained)
 {
        /* If the caller just changed the username/password etc, then
@@ -346,21 +398,27 @@ static int free_gssapi_creds(struct gssapi_creds_container *gcc)
        return 0;
 }
 
-int cli_credentials_get_client_gss_creds(struct cli_credentials *cred, 
-                                        struct loadparm_context *lp_ctx,
-                                        struct gssapi_creds_container **_gcc) 
+_PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred, 
+                                                 struct tevent_context *event_ctx,
+                                                 struct loadparm_context *lp_ctx,
+                                                 struct gssapi_creds_container **_gcc,
+                                                 const char **error_string)
 {
        int ret = 0;
        OM_uint32 maj_stat, min_stat;
        struct gssapi_creds_container *gcc;
        struct ccache_container *ccache;
+       gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
+       krb5_enctype *etypes = NULL;
+
        if (cred->client_gss_creds_obtained >= cred->client_gss_creds_threshold && 
            cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
                *_gcc = cred->client_gss_creds;
                return 0;
        }
-       ret = cli_credentials_get_ccache(cred, lp_ctx, 
-                                        &ccache);
+
+       ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
+                                        &ccache, error_string);
        if (ret) {
                DEBUG(1, ("Failed to get CCACHE for GSSAPI client: %s\n", error_message(ret)));
                return ret;
@@ -368,25 +426,93 @@ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
 
        gcc = talloc(cred, struct gssapi_creds_container);
        if (!gcc) {
+               (*error_string) = error_message(ENOMEM);
                return ENOMEM;
        }
 
        maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL, 
                                        &gcc->creds);
+       if ((maj_stat == GSS_S_FAILURE) && (min_stat == (OM_uint32)KRB5_CC_END || min_stat == (OM_uint32) KRB5_CC_NOTFOUND)) {
+               /* This CCACHE is no good.  Ensure we don't use it again */
+               cli_credentials_unconditionally_invalidate_ccache(cred);
+
+               /* Now try again to get a ccache */
+               ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
+                                                &ccache, error_string);
+               if (ret) {
+                       DEBUG(1, ("Failed to re-get CCACHE for GSSAPI client: %s\n", error_message(ret)));
+                       return ret;
+               }
+
+               maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL,
+                                               &gcc->creds);
+
+       }
+
        if (maj_stat) {
+               talloc_free(gcc);
                if (min_stat) {
                        ret = min_stat;
                } else {
                        ret = EINVAL;
                }
+               (*error_string) = talloc_asprintf(cred, "gss_krb5_import_cred failed: %s", error_message(ret));
+               return ret;
        }
-       if (ret == 0) {
-               cred->client_gss_creds_obtained = cred->ccache_obtained;
-               talloc_set_destructor(gcc, free_gssapi_creds);
-               cred->client_gss_creds = gcc;
-               *_gcc = gcc;
+
+       /*
+        * transfer the enctypes from the smb_krb5_context to the gssapi layer
+        *
+        * We use 'our' smb_krb5_context to do the AS-REQ and it is possible
+        * to configure the enctypes via the krb5.conf.
+        *
+        * And the gss_init_sec_context() creates it's own krb5_context and
+        * the TGS-REQ had all enctypes in it and only the ones configured
+        * and used for the AS-REQ, so it wasn't possible to disable the usage
+        * of AES keys.
+        */
+       min_stat = krb5_get_default_in_tkt_etypes(ccache->smb_krb5_context->krb5_context,
+                                                 &etypes);
+       if (min_stat == 0) {
+               OM_uint32 num_ktypes;
+
+               for (num_ktypes = 0; etypes[num_ktypes]; num_ktypes++);
+
+               maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds,
+                                                          num_ktypes, etypes);
+               krb5_xfree (etypes);
+               if (maj_stat) {
+                       talloc_free(gcc);
+                       if (min_stat) {
+                               ret = min_stat;
+                       } else {
+                               ret = EINVAL;
+                       }
+                       (*error_string) = talloc_asprintf(cred, "gss_krb5_set_allowable_enctypes failed: %s", error_message(ret));
+                       return ret;
+               }
        }
-       return ret;
+
+       /* don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG */
+       maj_stat = gss_set_cred_option(&min_stat, &gcc->creds,
+                                      GSS_KRB5_CRED_NO_CI_FLAGS_X,
+                                      &empty_buffer);
+       if (maj_stat) {
+               talloc_free(gcc);
+               if (min_stat) {
+                       ret = min_stat;
+               } else {
+                       ret = EINVAL;
+               }
+               (*error_string) = talloc_asprintf(cred, "gss_set_cred_option failed: %s", error_message(ret));
+               return ret;
+       }
+
+       cred->client_gss_creds_obtained = cred->ccache_obtained;
+       talloc_set_destructor(gcc, free_gssapi_creds);
+       cred->client_gss_creds = gcc;
+       *_gcc = gcc;
+       return 0;
 }
 
 /**
@@ -401,9 +527,11 @@ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
 */
 
  int cli_credentials_set_client_gss_creds(struct cli_credentials *cred, 
+                                         struct tevent_context *event_ctx,
                                          struct loadparm_context *lp_ctx,
                                          gss_cred_id_t gssapi_cred,
-                                         enum credentials_obtained obtained) 
+                                         enum credentials_obtained obtained,
+                                         const char **error_string)
 {
        int ret;
        OM_uint32 maj_stat, min_stat;
@@ -415,10 +543,11 @@ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
 
        gcc = talloc(cred, struct gssapi_creds_container);
        if (!gcc) {
+               (*error_string) = error_message(ENOMEM);
                return ENOMEM;
        }
 
-       ret = cli_credentials_new_ccache(cred, lp_ctx, &ccc);
+       ret = cli_credentials_new_ccache(cred, event_ctx, lp_ctx, NULL, &ccc, error_string);
        if (ret != 0) {
                return ret;
        }
@@ -431,10 +560,13 @@ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
                } else {
                        ret = EINVAL;
                }
+               if (ret) {
+                       (*error_string) = error_message(ENOMEM);
+               }
        }
 
        if (ret == 0) {
-               ret = cli_credentials_set_from_ccache(cred, ccc, obtained);
+               ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
        }
        cred->ccache = ccc;
        cred->ccache_obtained = obtained;
@@ -454,7 +586,8 @@ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
  * attached to this context.  If this hasn't been done or set before,
  * it will be generated from the password.
  */
-int cli_credentials_get_keytab(struct cli_credentials *cred, 
+_PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred, 
+                                       struct tevent_context *event_ctx,
                               struct loadparm_context *lp_ctx,
                               struct keytab_container **_ktc)
 {
@@ -474,7 +607,7 @@ int cli_credentials_get_keytab(struct cli_credentials *cred,
                return EINVAL;
        }
 
-       ret = cli_credentials_get_krb5_context(cred, lp_ctx, 
+       ret = cli_credentials_get_krb5_context(cred, event_ctx, lp_ctx,
                                               &smb_krb5_context);
        if (ret) {
                return ret;
@@ -508,7 +641,8 @@ int cli_credentials_get_keytab(struct cli_credentials *cred,
 /* Given the name of a keytab (presumably in the format
  * FILE:/etc/krb5.keytab), open it and attach it */
 
-int cli_credentials_set_keytab_name(struct cli_credentials *cred, 
+_PUBLIC_ int cli_credentials_set_keytab_name(struct cli_credentials *cred, 
+                                            struct tevent_context *event_ctx,
                                    struct loadparm_context *lp_ctx,
                                    const char *keytab_name, 
                                    enum credentials_obtained obtained) 
@@ -522,7 +656,7 @@ int cli_credentials_set_keytab_name(struct cli_credentials *cred,
                return 0;
        }
 
-       ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
+       ret = cli_credentials_get_krb5_context(cred, event_ctx, lp_ctx, &smb_krb5_context);
        if (ret) {
                return ret;
        }
@@ -547,8 +681,9 @@ int cli_credentials_set_keytab_name(struct cli_credentials *cred,
        return ret;
 }
 
-int cli_credentials_update_keytab(struct cli_credentials *cred, 
-                                 struct loadparm_context *lp_ctx) 
+_PUBLIC_ int cli_credentials_update_keytab(struct cli_credentials *cred, 
+                                          struct tevent_context *event_ctx,
+                                 struct loadparm_context *lp_ctx)
 {
        krb5_error_code ret;
        struct keytab_container *ktc;
@@ -561,7 +696,7 @@ int cli_credentials_update_keytab(struct cli_credentials *cred,
                return ENOMEM;
        }
 
-       ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
+       ret = cli_credentials_get_krb5_context(cred, event_ctx, lp_ctx, &smb_krb5_context);
        if (ret) {
                talloc_free(mem_ctx);
                return ret;
@@ -569,7 +704,7 @@ int cli_credentials_update_keytab(struct cli_credentials *cred,
 
        enctype_strings = cli_credentials_get_enctype_strings(cred);
        
-       ret = cli_credentials_get_keytab(cred, lp_ctx, &ktc);
+       ret = cli_credentials_get_keytab(cred, event_ctx, lp_ctx, &ktc);
        if (ret != 0) {
                talloc_free(mem_ctx);
                return ret;
@@ -583,7 +718,8 @@ int cli_credentials_update_keytab(struct cli_credentials *cred,
 
 /* Get server gss credentials (in gsskrb5, this means the keytab) */
 
-int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, 
+_PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, 
+                                                 struct tevent_context *event_ctx,
                                         struct loadparm_context *lp_ctx,
                                         struct gssapi_creds_container **_gcc) 
 {
@@ -594,36 +730,36 @@ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred,
        struct smb_krb5_context *smb_krb5_context;
        TALLOC_CTX *mem_ctx;
        krb5_principal princ;
+       const char *error_string;
+       enum credentials_obtained obtained;
 
-       if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, 
-                                                   MAX(cred->principal_obtained, 
-                                                       cred->username_obtained)))) {
-               *_gcc = cred->server_gss_creds;
-               return 0;
+       mem_ctx = talloc_new(cred);
+       if (!mem_ctx) {
+               return ENOMEM;
        }
 
-       ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
+       ret = cli_credentials_get_krb5_context(cred, event_ctx, lp_ctx, &smb_krb5_context);
        if (ret) {
                return ret;
        }
 
-       ret = cli_credentials_get_keytab(cred, lp_ctx, &ktc);
+       ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ, &obtained, &error_string);
        if (ret) {
-               DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
+               DEBUG(1,("cli_credentials_get_server_gss_creds: makeing krb5 principal failed (%s)\n",
+                        error_string));
+               talloc_free(mem_ctx);
                return ret;
        }
 
-       mem_ctx = talloc_new(cred);
-       if (!mem_ctx) {
-               return ENOMEM;
+       if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, obtained))) {
+               talloc_free(mem_ctx);
+               *_gcc = cred->server_gss_creds;
+               return 0;
        }
 
-       ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ);
+       ret = cli_credentials_get_keytab(cred, event_ctx, lp_ctx, &ktc);
        if (ret) {
-               DEBUG(1,("cli_credentials_get_server_gss_creds: makeing krb5 principal failed (%s)\n",
-                        smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
-                                                   ret, mem_ctx)));
-               talloc_free(mem_ctx);
+               DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
                return ret;
        }
 
@@ -657,7 +793,7 @@ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred,
  * Set Kerberos KVNO
  */
 
-void cli_credentials_set_kvno(struct cli_credentials *cred,
+_PUBLIC_ void cli_credentials_set_kvno(struct cli_credentials *cred,
                              int kvno)
 {
        cred->kvno = kvno;
@@ -667,7 +803,7 @@ void cli_credentials_set_kvno(struct cli_credentials *cred,
  * Return Kerberos KVNO
  */
 
-int cli_credentials_get_kvno(struct cli_credentials *cred)
+_PUBLIC_ int cli_credentials_get_kvno(struct cli_credentials *cred)
 {
        return cred->kvno;
 }
@@ -693,9 +829,48 @@ const char *cli_credentials_get_salt_principal(struct cli_credentials *cred)
        return cred->salt_principal;
 }
 
-void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) 
+_PUBLIC_ void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) 
 {
+       talloc_free(cred->salt_principal);
        cred->salt_principal = talloc_strdup(cred, principal);
 }
 
+/* The 'impersonate_principal' is used to allow on Kerberos principal
+ * (and it's associated keytab etc) to impersonate another.  The
+ * ability to do this is controlled by the KDC, but it is generally
+ * permitted to impersonate anyone to yourself.  This allows any
+ * member of the domain to get the groups of a user.  This is also
+ * known as S4U2Self */
+
+const char *cli_credentials_get_impersonate_principal(struct cli_credentials *cred)
+{
+       return cred->impersonate_principal;
+}
+
+_PUBLIC_ void cli_credentials_set_impersonate_principal(struct cli_credentials *cred, const char *principal)
+{
+       talloc_free(cred->impersonate_principal);
+       cred->impersonate_principal = talloc_strdup(cred, principal);
+}
+
+/* when impersonating for S4U2Self we need to set the target principal
+ * to ourself, as otherwise we would need additional rights.
+ * Similarly, we may only be authorized to do general impersonation to
+ * some particular services.
+ *
+ * Likewise, password changes typically require a ticket to kpasswd/realm directly, not via a TGT
+ *
+ * NULL means that tickets will be obtained for the krbtgt service.
+*/
+
+const char *cli_credentials_get_target_service(struct cli_credentials *cred)
+{
+       return cred->target_service;
+}
+
+_PUBLIC_ void cli_credentials_set_target_service(struct cli_credentials *cred, const char *target_service)
+{
+       talloc_free(cred->target_service);
+       cred->target_service = talloc_strdup(cred, target_service);
+}