int cli_credentials_get_ccache(struct cli_credentials *cred, struct ccache_container **ccc) { krb5_error_code ret; if (cred->ccache_obtained >= (MAX(cred->principal_obtained, cred->username_obtained))) { *ccc = cred->ccache; return 0; } if (cli_credentials_is_anonymous(cred)) { return EINVAL; } ret = cli_credentials_new_ccache(cred, NULL); if (ret) { return ret; } ret = kinit_to_ccache(cred, cred, cred->ccache->smb_krb5_context, cred->ccache->ccache); if (ret) { return ret; } ret = cli_credentials_set_from_ccache(cred, cred->principal_obtained); if (ret) { return ret; } *ccc = cred->ccache; return ret; }
krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx, struct cli_credentials *credentials, struct smb_krb5_context *smb_krb5_context, struct tevent_context *event_ctx, krb5_ccache ccache, enum credentials_obtained *obtained, const char **error_string) { krb5_error_code ret; const char *password; #ifdef SAMBA4_USES_HEIMDAL const char *self_service; #endif const char *target_service; time_t kdc_time = 0; krb5_principal princ; krb5_principal impersonate_principal; int tries; TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); krb5_get_init_creds_opt *krb_options; if (!mem_ctx) { (*error_string) = strerror(ENOMEM); return ENOMEM; } ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ, obtained, error_string); if (ret) { talloc_free(mem_ctx); return ret; } if (princ == NULL) { (*error_string) = talloc_asprintf(credentials, "principal, username or realm was not specified in the credentials"); talloc_free(mem_ctx); return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; } ret = impersonate_principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &impersonate_principal, error_string); if (ret) { talloc_free(mem_ctx); return ret; } #ifdef SAMBA4_USES_HEIMDAL self_service = cli_credentials_get_self_service(credentials); #endif target_service = cli_credentials_get_target_service(credentials); password = cli_credentials_get_password(credentials); /* setup the krb5 options we want */ if ((ret = krb5_get_init_creds_opt_alloc(smb_krb5_context->krb5_context, &krb_options))) { (*error_string) = talloc_asprintf(credentials, "krb5_get_init_creds_opt_alloc failed (%s)\n", smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx)); talloc_free(mem_ctx); return ret; } #ifdef SAMBA4_USES_HEIMDAL /* Disable for now MIT reads defaults when needed */ /* get the defaults */ krb5_get_init_creds_opt_set_default_flags(smb_krb5_context->krb5_context, NULL, NULL, krb_options); #endif /* set if we want a forwardable ticket */ switch (cli_credentials_get_krb_forwardable(credentials)) { case CRED_AUTO_KRB_FORWARDABLE: break; case CRED_NO_KRB_FORWARDABLE: krb5_get_init_creds_opt_set_forwardable(krb_options, FALSE); break; case CRED_FORCE_KRB_FORWARDABLE: krb5_get_init_creds_opt_set_forwardable(krb_options, TRUE); break; } #ifdef SAMBA4_USES_HEIMDAL /* FIXME: MIT does not have this yet */ /* * In order to work against windows KDCs even if we use * the netbios domain name as realm, we need to add the following * flags: * KRB5_INIT_CREDS_NO_C_CANON_CHECK; * KRB5_INIT_CREDS_NO_C_NO_EKU_CHECK; * * On MIT: Set pkinit_eku_checking to none */ krb5_get_init_creds_opt_set_win2k(smb_krb5_context->krb5_context, krb_options, true); #else /* MIT */ krb5_get_init_creds_opt_set_canonicalize(krb_options, true); #endif tries = 2; while (tries--) { #ifdef SAMBA4_USES_HEIMDAL struct tevent_context *previous_ev; /* Do this every time, in case we have weird recursive issues here */ ret = smb_krb5_context_set_event_ctx(smb_krb5_context, event_ctx, &previous_ev); if (ret) { talloc_free(mem_ctx); return ret; } #endif if (password) { if (impersonate_principal) { #ifdef SAMBA4_USES_HEIMDAL ret = kerberos_kinit_s4u2_cc( smb_krb5_context->krb5_context, ccache, princ, password, impersonate_principal, self_service, target_service, krb_options, NULL, &kdc_time); #else talloc_free(mem_ctx); (*error_string) = "INTERNAL error: s4u2 ops " "are not supported with MIT build yet"; return EINVAL; #endif } else { ret = kerberos_kinit_password_cc( smb_krb5_context->krb5_context, ccache, princ, password, target_service, krb_options, NULL, &kdc_time); } } else if (impersonate_principal) { talloc_free(mem_ctx); (*error_string) = "INTERNAL error: Cannot impersonate principal with just a keyblock. A password must be specified in the credentials"; return EINVAL; } else { /* No password available, try to use a keyblock instead */ krb5_keyblock keyblock; const struct samr_Password *mach_pwd; mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx); if (!mach_pwd) { talloc_free(mem_ctx); (*error_string) = "kinit_to_ccache: No password available for kinit\n"; krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options); #ifdef SAMBA4_USES_HEIMDAL smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx); #endif return EINVAL; } ret = smb_krb5_keyblock_init_contents(smb_krb5_context->krb5_context, ENCTYPE_ARCFOUR_HMAC, mach_pwd->hash, sizeof(mach_pwd->hash), &keyblock); if (ret == 0) { ret = kerberos_kinit_keyblock_cc(smb_krb5_context->krb5_context, ccache, princ, &keyblock, target_service, krb_options, NULL, &kdc_time); krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock); } } #ifdef SAMBA4_USES_HEIMDAL smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx); #endif if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { /* Perhaps we have been given an invalid skew, so try again without it */ time_t t = time(NULL); krb5_set_real_time(smb_krb5_context->krb5_context, t, 0); } else { /* not a skew problem */ break; } } krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options); if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n", cli_credentials_get_principal(credentials, mem_ctx), smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx)); talloc_free(mem_ctx); return ret; } /* cope with ticket being in the future due to clock skew */ if ((unsigned)kdc_time > time(NULL)) { time_t t = time(NULL); int time_offset =(unsigned)kdc_time-t; DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset)); krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0); } if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) { ret = kinit_to_ccache(parent_ctx, credentials, smb_krb5_context, event_ctx, ccache, obtained, error_string); } if (ret) { (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n", cli_credentials_get_principal(credentials, mem_ctx), smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx)); talloc_free(mem_ctx); return ret; } DEBUG(10,("kinit for %s succeeded\n", cli_credentials_get_principal(credentials, mem_ctx))); talloc_free(mem_ctx); return 0; }
_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); } if (cred->ccache_obtained >= cred->ccache_threshold && cred->ccache_obtained > CRED_UNINITIALISED) { time_t lifetime; bool expired = false; ret = smb_krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context, cred->ccache->ccache, &lifetime); if (ret == KRB5_CC_END) { /* If we have a particular ccache set, without * an initial ticket, then assume there is a * good reason */ } else if (ret == 0) { if (lifetime == 0) { DEBUG(3, ("Ticket in credentials cache for %s expired, will refresh\n", cli_credentials_get_principal(cred, cred))); expired = true; } else if (lifetime < 300) { DEBUG(3, ("Ticket in credentials cache for %s will shortly expire (%u secs), will refresh\n", cli_credentials_get_principal(cred, cred), (unsigned int)lifetime)); expired = true; } } else { (*error_string) = talloc_asprintf(cred, "failed to get ccache lifetime: %s\n", smb_get_krb5_error_message(cred->ccache->smb_krb5_context->krb5_context, ret, cred)); return ret; } DEBUG(5, ("Ticket in credentials cache for %s will expire in %u secs\n", cli_credentials_get_principal(cred, cred), (unsigned int)lifetime)); if (!expired) { *ccc = cred->ccache; 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, ccache_name, ccc, error_string); if (ret) { return ret; } ret = kinit_to_ccache(cred, cred, (*ccc)->smb_krb5_context, event_ctx, (*ccc)->ccache, &obtained, error_string); if (ret) { return ret; } ret = cli_credentials_set_from_ccache(cred, *ccc, obtained, error_string); cred->ccache = *ccc; cred->ccache_obtained = cred->principal_obtained; if (ret) { return ret; } cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained); return 0; }
krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx, struct cli_credentials *credentials, struct smb_krb5_context *smb_krb5_context, krb5_ccache ccache) { krb5_error_code ret; const char *password; time_t kdc_time = 0; krb5_principal princ; int tries; TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); if (!mem_ctx) { return ENOMEM; } ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ); if (ret) { talloc_free(mem_ctx); return ret; } password = cli_credentials_get_password(credentials); tries = 2; while (tries--) { if (password) { ret = kerberos_kinit_password_cc(smb_krb5_context->krb5_context, ccache, princ, password, NULL, &kdc_time); } else { /* No password available, try to use a keyblock instead */ krb5_keyblock keyblock; const struct samr_Password *mach_pwd; mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx); if (!mach_pwd) { talloc_free(mem_ctx); DEBUG(1, ("kinit_to_ccache: No password available for kinit\n")); return EINVAL; } ret = krb5_keyblock_init(smb_krb5_context->krb5_context, ENCTYPE_ARCFOUR_HMAC, mach_pwd->hash, sizeof(mach_pwd->hash), &keyblock); if (ret == 0) { ret = kerberos_kinit_keyblock_cc(smb_krb5_context->krb5_context, ccache, princ, &keyblock, NULL, &kdc_time); krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock); } } if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { /* Perhaps we have been given an invalid skew, so try again without it */ time_t t = time(NULL); krb5_set_real_time(smb_krb5_context->krb5_context, t, 0); } else { /* not a skew problem */ break; } } if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { DEBUG(1,("kinit for %s failed (%s)\n", cli_credentials_get_principal(credentials, mem_ctx), smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx))); talloc_free(mem_ctx); return ret; } /* cope with ticket being in the future due to clock skew */ if ((unsigned)kdc_time > time(NULL)) { time_t t = time(NULL); int time_offset =(unsigned)kdc_time-t; DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset)); krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0); } if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) { ret = kinit_to_ccache(parent_ctx, credentials, smb_krb5_context, ccache); } if (ret) { DEBUG(1,("kinit for %s failed (%s)\n", cli_credentials_get_principal(credentials, mem_ctx), smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx))); talloc_free(mem_ctx); return ret; } talloc_free(mem_ctx); return 0; }