summaryrefslogtreecommitdiff
path: root/source4/libcli/auth
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2004-07-13 05:14:59 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 12:57:34 -0500
commited03516c915c4a4c8ae6f7decfa04d51049d9dd5 (patch)
tree41f535a24108d59c367849ae80885198e371bda3 /source4/libcli/auth
parent39b12015846e06cbf89079e365e6c228ca3883c2 (diff)
downloadsamba-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.c189
-rw-r--r--source4/libcli/auth/gensec.h2
-rw-r--r--source4/libcli/auth/gensec_krb5.c114
-rw-r--r--source4/libcli/auth/kerberos.c64
-rw-r--r--source4/libcli/auth/kerberos.h1
-rw-r--r--source4/libcli/auth/spnego.c3
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)) {