diff options
author | Andrew Bartlett <abartlet@samba.org> | 2004-07-13 05:14:59 +0000 |
---|---|---|
committer | Gerald (Jerry) Carter <jerry@samba.org> | 2007-10-10 12:57:34 -0500 |
commit | ed03516c915c4a4c8ae6f7decfa04d51049d9dd5 (patch) | |
tree | 41f535a24108d59c367849ae80885198e371bda3 /source4/libcli/auth | |
parent | 39b12015846e06cbf89079e365e6c228ca3883c2 (diff) | |
download | samba-ed03516c915c4a4c8ae6f7decfa04d51049d9dd5.tar.gz samba-ed03516c915c4a4c8ae6f7decfa04d51049d9dd5.tar.bz2 samba-ed03516c915c4a4c8ae6f7decfa04d51049d9dd5.zip |
r1475: More kerberos work
- We can now connect to hosts that follow the SPNEGO RFC, and *do not*
give us their principal name in the mechListMIC.
- The client code now remembers the hostname it connects to
- We now kinit for a user, if there is not valid ticket already
- Re-introduce clock skew compensation
TODO:
- See if the username in the ccache matches the username specified
- Use a private ccache, rather then the global one, for a 'new' kinit
- Determine 'default' usernames.
- The default for Krb5 is the one in the ccache, then $USER
- For NTLMSSP, it's just $USER
Andrew Bartlett
(This used to be commit de5da669397db4ac87c6da08d3533ca3030da2b0)
Diffstat (limited to 'source4/libcli/auth')
-rw-r--r-- | source4/libcli/auth/gensec.c | 189 | ||||
-rw-r--r-- | source4/libcli/auth/gensec.h | 2 | ||||
-rw-r--r-- | source4/libcli/auth/gensec_krb5.c | 114 | ||||
-rw-r--r-- | source4/libcli/auth/kerberos.c | 64 | ||||
-rw-r--r-- | source4/libcli/auth/kerberos.h | 1 | ||||
-rw-r--r-- | source4/libcli/auth/spnego.c | 3 |
6 files changed, 321 insertions, 52 deletions
diff --git a/source4/libcli/auth/gensec.c b/source4/libcli/auth/gensec.c index f4aeedf692..e91497bee4 100644 --- a/source4/libcli/auth/gensec.c +++ b/source4/libcli/auth/gensec.c @@ -106,6 +106,14 @@ static NTSTATUS gensec_start(struct gensec_security **gensec_security) (*gensec_security)->mem_ctx = mem_ctx; (*gensec_security)->ops = NULL; + ZERO_STRUCT((*gensec_security)->user); + ZERO_STRUCT((*gensec_security)->target); + ZERO_STRUCT((*gensec_security)->default_user); + + (*gensec_security)->default_user.name = ""; + (*gensec_security)->default_user.domain = talloc_strdup(mem_ctx, lp_workgroup()); + (*gensec_security)->default_user.realm = talloc_strdup(mem_ctx, lp_realm()); + (*gensec_security)->subcontext = False; return NT_STATUS_OK; } @@ -163,6 +171,9 @@ NTSTATUS gensec_server_start(struct gensec_security **gensec_security) static NTSTATUS gensec_start_mech(struct gensec_security *gensec_security) { NTSTATUS status; + DEBUG(5, ("Starting GENSEC %smechanism %s\n", + gensec_security->subcontext ? "sub" : "", + gensec_security->ops->name)); switch (gensec_security->gensec_role) { case GENSEC_CLIENT: if (gensec_security->ops->client_start) { @@ -342,6 +353,61 @@ void gensec_end(struct gensec_security **gensec_security) * */ +NTSTATUS gensec_set_unparsed_username(struct gensec_security *gensec_security, const char *user) +{ + char *p; + char *u = talloc_strdup(gensec_security->mem_ctx, user); + if (!u) { + return NT_STATUS_NO_MEMORY; + } + + p = strchr_m(user, '@'); + + if (p) { + *p = '\0'; + gensec_security->user.name = talloc_strdup(gensec_security->mem_ctx, u); + if (!gensec_security->user.name) { + return NT_STATUS_NO_MEMORY; + } + + gensec_security->user.realm = talloc_strdup(gensec_security->mem_ctx, p+1); + if (!gensec_security->user.realm) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; + } + + p = strchr_m(user, '\\'); + if (!p) { + p = strchr_m(user, '/'); + } + + if (p) { + *p = '\0'; + gensec_security->user.domain = talloc_strdup(gensec_security->mem_ctx, u); + if (!gensec_security->user.domain) { + return NT_STATUS_NO_MEMORY; + } + gensec_security->user.name = talloc_strdup(gensec_security->mem_ctx, p+1); + if (!gensec_security->user.name) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; + } + + gensec_security->user.name = u; + if (!gensec_security->user.name) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Set a username on a GENSEC context - ensures it is talloc()ed + * + */ + NTSTATUS gensec_set_username(struct gensec_security *gensec_security, const char *user) { gensec_security->user.name = talloc_strdup(gensec_security->mem_ctx, user); @@ -352,6 +418,19 @@ NTSTATUS gensec_set_username(struct gensec_security *gensec_security, const char } /** + * Set a username on a GENSEC context - ensures it is talloc()ed + * + */ + +const char *gensec_get_username(struct gensec_security *gensec_security) +{ + if (gensec_security->user.name) { + return gensec_security->user.name; + } + return gensec_security->default_user.name; +} + +/** * Set a domain on a GENSEC context - ensures it is talloc()ed * */ @@ -366,19 +445,18 @@ NTSTATUS gensec_set_domain(struct gensec_security *gensec_security, const char * } /** - * Set the password outright on GENSEC context - ensures it is talloc()ed, and that we will - * not do a callback + * Return the NT domain for this GENSEC context * */ -NTSTATUS gensec_set_password(struct gensec_security *gensec_security, - const char *password) +const char *gensec_get_domain(struct gensec_security *gensec_security) { - gensec_security->user.password = talloc_strdup(gensec_security->mem_ctx, password); - if (!gensec_security->user.password) { - return NT_STATUS_NO_MEMORY; + if (gensec_security->user.domain) { + return gensec_security->user.domain; + } else if (gensec_security->user.realm) { + return gensec_security->user.realm; } - return NT_STATUS_OK; + return gensec_security->default_user.domain; } /** @@ -396,6 +474,54 @@ NTSTATUS gensec_set_realm(struct gensec_security *gensec_security, const char *r } /** + * Return the Krb5 realm for this context + * + */ + +const char *gensec_get_realm(struct gensec_security *gensec_security) +{ + if (gensec_security->user.realm) { + return gensec_security->user.realm; + } else if (gensec_security->user.domain) { + return gensec_security->user.domain; + } + return gensec_security->default_user.realm; +} + +/** + * Return a kerberos principal for this context, if one has been set + * + */ + +char *gensec_get_client_principal(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx) +{ + const char *realm = gensec_get_realm(gensec_security); + if (realm) { + return talloc_asprintf(mem_ctx, "%s@%s", + gensec_get_username(gensec_security), + gensec_get_realm(gensec_security)); + } else { + return talloc_strdup(mem_ctx, gensec_get_username(gensec_security)); + } +} + +/** + * Set the password outright on GENSEC context - ensures it is talloc()ed, and that we will + * not do a callback + * + */ + +NTSTATUS gensec_set_password(struct gensec_security *gensec_security, + const char *password) +{ + gensec_security->user.password = talloc_strdup(gensec_security->mem_ctx, password); + if (!gensec_security->user.password) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** * Set the target principal name (if already known) on a GENSEC context - ensures it is talloc()ed * */ @@ -410,6 +536,53 @@ NTSTATUS gensec_set_target_principal(struct gensec_security *gensec_security, co } /** + * Set the target service (such as 'http' or 'host') on a GENSEC context - ensures it is talloc()ed + * + */ + +NTSTATUS gensec_set_target_service(struct gensec_security *gensec_security, const char *service) +{ + gensec_security->target.service = talloc_strdup(gensec_security->mem_ctx, service); + if (!gensec_security->target.service) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Set the target hostname (suitable for kerberos resolutation) on a GENSEC context - ensures it is talloc()ed + * + */ + +NTSTATUS gensec_set_target_hostname(struct gensec_security *gensec_security, const char *hostname) +{ + gensec_security->target.hostname = talloc_strdup(gensec_security->mem_ctx, hostname); + if (!gensec_security->target.hostname) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +const char *gensec_get_target_hostname(struct gensec_security *gensec_security) +{ + if (gensec_security->target.hostname) { + return gensec_security->target.hostname; + } + + /* TODO: Add a 'set sockaddr' call, and do a reverse lookup */ + return NULL; +} + +const char *gensec_get_target_service(struct gensec_security *gensec_security) +{ + if (gensec_security->target.service) { + return gensec_security->target.service; + } + + return "host"; +} + +/** * Set a password callback, if the gensec module we use demands a password */ diff --git a/source4/libcli/auth/gensec.h b/source4/libcli/auth/gensec.h index 7cd56936d2..8e2787530c 100644 --- a/source4/libcli/auth/gensec.h +++ b/source4/libcli/auth/gensec.h @@ -34,6 +34,7 @@ struct gensec_target { const char *principal; const char *hostname; const struct sock_addr *addr; + const char *service; }; @@ -79,6 +80,7 @@ struct gensec_security { const struct gensec_security_ops *ops; void *private_data; struct gensec_user user; + struct gensec_user default_user; struct gensec_target target; enum gensec_role gensec_role; BOOL subcontext; diff --git a/source4/libcli/auth/gensec_krb5.c b/source4/libcli/auth/gensec_krb5.c index 3a4f995937..78b6e334a7 100644 --- a/source4/libcli/auth/gensec_krb5.c +++ b/source4/libcli/auth/gensec_krb5.c @@ -43,6 +43,7 @@ struct gensec_krb5_state { krb5_context krb5_context; krb5_auth_context krb5_auth_context; krb5_ccache krb5_ccache; + krb5_data ticket; }; static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security) @@ -68,6 +69,7 @@ static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security) gensec_krb5_state->krb5_context = NULL; gensec_krb5_state->krb5_auth_context = NULL; gensec_krb5_state->krb5_ccache = NULL; + ZERO_STRUCT(gensec_krb5_state->ticket); gensec_krb5_state->session_key = data_blob(NULL, 0); ret = krb5_init_context(&gensec_krb5_state->krb5_context); @@ -114,6 +116,7 @@ static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security struct gensec_krb5_state *gensec_krb5_state; krb5_error_code ret; NTSTATUS nt_status; + nt_status = gensec_krb5_start(gensec_security); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; @@ -122,6 +125,7 @@ static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security gensec_krb5_state = gensec_security->private_data; gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START; + /* TODO: This is effecivly a static/global variable... */ ret = krb5_cc_default(gensec_krb5_state->krb5_context, &gensec_krb5_state->krb5_ccache); if (ret) { DEBUG(1,("krb5_cc_default failed (%s)\n", @@ -129,13 +133,101 @@ static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security return NT_STATUS_INTERNAL_ERROR; } - return NT_STATUS_OK; + while (1) { + if (gensec_security->target.principal) { + DEBUG(5, ("Finding ticket for target [%s]\n", gensec_security->target.principal)); + ret = ads_krb5_mk_req(gensec_krb5_state->krb5_context, + &gensec_krb5_state->krb5_auth_context, + AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED, + gensec_security->target.principal, + gensec_krb5_state->krb5_ccache, + &gensec_krb5_state->ticket); + if (ret) { + DEBUG(1,("ads_krb5_mk_req failed (%s)\n", + error_message(ret))); + } + } else { + krb5_data in_data; + in_data.length = 0; + const char *hostname = gensec_get_target_hostname(gensec_security); + if (!hostname) { + DEBUG(1, ("Could not determine hostname for target computer, cannot use kerberos\n")); + return NT_STATUS_ACCESS_DENIED; + } + ret = krb5_mk_req(gensec_krb5_state->krb5_context, + &gensec_krb5_state->krb5_auth_context, + AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED, + gensec_get_target_service(gensec_security), + hostname, + &in_data, gensec_krb5_state->krb5_ccache, + &gensec_krb5_state->ticket); + if (ret) { + DEBUG(1,("krb5_mk_req failed (%s)\n", + error_message(ret))); + } + + } + switch (ret) { + case 0: + return NT_STATUS_OK; + case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN: + DEBUG(3, ("Server is not registered with our KDC: %s\n", + error_message(ret))); + return NT_STATUS_ACCESS_DENIED; + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_TKT_EXPIRED: + case KRB5_CC_END: + case KRB5_FCC_NOFILE: + case KRB5_CC_NOTFOUND: + { + char *password; + time_t kdc_time; + DEBUG(3, ("kerberos: %s\n", + error_message(ret))); + nt_status = gensec_get_password(gensec_security, + gensec_security->mem_ctx, + &password); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + ret = kerberos_kinit_password_cc(gensec_krb5_state->krb5_context, gensec_krb5_state->krb5_ccache, + gensec_get_client_principal(gensec_security, gensec_security->mem_ctx), + password, NULL, &kdc_time); + + /* 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(gensec_krb5_state->krb5_context, t + time_offset + 1, 0); + } + + if (ret) { + DEBUG(1,("kinit failed (%s)\n", + error_message(ret))); + return NT_STATUS_WRONG_PASSWORD; + } + break; + } + default: + DEBUG(0, ("kerberos: %s\n", + error_message(ret))); + return NT_STATUS_UNSUCCESSFUL; + } + } } static void gensec_krb5_end(struct gensec_security *gensec_security) { struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data; + if (gensec_krb5_state->ticket.length) { + /* Hmm, heimdal dooesn't have this - what's the correct call? */ +#ifdef HAVE_KRB5_FREE_DATA_CONTENTS + krb5_free_data_contents(gensec_krb5_state->krb5_context, &gensec_krb5_state->ticket); +#endif + } if (gensec_krb5_state->krb5_ccache) { /* Removed by jra. They really need to fix their kerberos so we don't leak memory. JERRY -- disabled since it causes heimdal 0.6.1rc3 to die @@ -182,33 +274,17 @@ static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security, TALL switch (gensec_krb5_state->state_position) { case GENSEC_KRB5_CLIENT_START: { - krb5_data packet; - -#if 0 /* When we get some way to input the time offset */ - if (time_offset != 0) { - krb5_set_real_time(context, time(NULL) + time_offset, 0); - } -#endif - - ret = ads_krb5_mk_req(gensec_krb5_state->krb5_context, - &gensec_krb5_state->krb5_auth_context, - AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED, - gensec_security->target.principal, - gensec_krb5_state->krb5_ccache, &packet); if (ret) { DEBUG(1,("ads_krb5_mk_req (request ticket) failed (%s)\n", error_message(ret))); nt_status = NT_STATUS_LOGON_FAILURE; } else { DATA_BLOB unwrapped_out; - unwrapped_out = data_blob_talloc(out_mem_ctx, packet.data, packet.length); + unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->ticket.data, gensec_krb5_state->ticket.length); /* wrap that up in a nice GSS-API wrapping */ *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ); - /* Hmm, heimdal dooesn't have this - what's the correct call? */ -#ifdef HAVE_KRB5_FREE_DATA_CONTENTS - krb5_free_data_contents(gensec_krb5_state->krb5_context, &packet); -#endif + gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH; nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; } diff --git a/source4/libcli/auth/kerberos.c b/source4/libcli/auth/kerberos.c index 97b895a241..b08c7f505c 100644 --- a/source4/libcli/auth/kerberos.c +++ b/source4/libcli/auth/kerberos.c @@ -54,28 +54,13 @@ kerb_prompter(krb5_context ctx, void *data, simulate a kinit, putting the tgt in the default cache location remus@snapserver.com */ -int kerberos_kinit_password(const char *principal, const char *password, int time_offset, time_t *expire_time) + int kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc, const char *principal, const char *password, time_t *expire_time, time_t *kdc_time) { - krb5_context ctx = NULL; krb5_error_code code = 0; - krb5_ccache cc = NULL; krb5_principal me; krb5_creds my_creds; - if ((code = krb5_init_context(&ctx))) - return code; - - if (time_offset != 0) { - krb5_set_real_time(ctx, time(NULL) + time_offset, 0); - } - - if ((code = krb5_cc_default(ctx, &cc))) { - krb5_free_context(ctx); - return code; - } - if ((code = krb5_parse_name(ctx, principal, &me))) { - krb5_free_context(ctx); return code; } @@ -83,32 +68,63 @@ int kerberos_kinit_password(const char *principal, const char *password, int tim kerb_prompter, NULL, 0, NULL, NULL))) { krb5_free_principal(ctx, me); - krb5_free_context(ctx); return code; } if ((code = krb5_cc_initialize(ctx, cc, me))) { krb5_free_cred_contents(ctx, &my_creds); krb5_free_principal(ctx, me); - krb5_free_context(ctx); return code; } if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) { - krb5_cc_close(ctx, cc); krb5_free_cred_contents(ctx, &my_creds); krb5_free_principal(ctx, me); - krb5_free_context(ctx); return code; } - if (expire_time) + if (expire_time) { *expire_time = (time_t) my_creds.times.endtime; + } + + if (kdc_time) { + *kdc_time = (time_t) my_creds.times.starttime; + } - krb5_cc_close(ctx, cc); krb5_free_cred_contents(ctx, &my_creds); krb5_free_principal(ctx, me); - krb5_free_context(ctx); + + return 0; +} + + +/* + simulate a kinit, putting the tgt in the default cache location + remus@snapserver.com +*/ +int kerberos_kinit_password(const char *principal, const char *password, int time_offset, time_t *expire_time, time_t *kdc_time) +{ + krb5_context ctx = NULL; + krb5_error_code code = 0; + krb5_ccache cc = NULL; + + if ((code = krb5_init_context(&ctx))) + return code; + + if (time_offset != 0) { + krb5_set_real_time(ctx, time(NULL) + time_offset, 0); + } + + if ((code = krb5_cc_default(ctx, &cc))) { + krb5_free_context(ctx); + return code; + } + + if ((code = kerberos_kinit_password_cc(ctx, cc, principal, password, expire_time, kdc_time))) { + krb5_cc_close(ctx, cc); + krb5_free_context(ctx); + return code; + } return 0; } @@ -129,7 +145,7 @@ int ads_kinit_password(ADS_STRUCT *ads) return KRB5_LIBOS_CANTREADPWD; } - ret = kerberos_kinit_password(s, ads->auth.password, ads->auth.time_offset, &ads->auth.expire); + ret = kerberos_kinit_password(s, ads->auth.password, ads->auth.time_offset, &ads->auth.expire, NULL); if (ret) { DEBUG(0,("kerberos_kinit_password %s failed: %s\n", diff --git a/source4/libcli/auth/kerberos.h b/source4/libcli/auth/kerberos.h index 4a9d82acf1..ca796d0c86 100644 --- a/source4/libcli/auth/kerberos.h +++ b/source4/libcli/auth/kerberos.h @@ -69,5 +69,6 @@ NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx, const char *realm, const DATA_BLOB *ticket, char **principal, DATA_BLOB *auth_data, DATA_BLOB *ap_rep); +int kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc, const char *principal, const char *password, time_t *expire_time, time_t *kdc_time); #endif /* HAVE_KRB5 */ diff --git a/source4/libcli/auth/spnego.c b/source4/libcli/auth/spnego.c index 32846cf580..d4910eb92f 100644 --- a/source4/libcli/auth/spnego.c +++ b/source4/libcli/auth/spnego.c @@ -256,7 +256,7 @@ static NTSTATUS gensec_spnego_client_netTokenInit(struct gensec_security *gensec return nt_status; } nt_status = gensec_update(spnego_state->sub_sec_security, - out_mem_ctx, in, &unwrapped_out); + out_mem_ctx, in, &unwrapped_out); if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { struct spnego_data spnego_out; spnego_out.type = SPNEGO_NEG_TOKEN_INIT; @@ -349,6 +349,7 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA } if (spnego.negTokenInit.targetPrincipal) { + DEBUG(5, ("Server claims it's principal name is %s\n", spnego.negTokenInit.targetPrincipal)); nt_status = gensec_set_target_principal(gensec_security, spnego.negTokenInit.targetPrincipal); if (!NT_STATUS_IS_OK(nt_status)) { |