[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: RFC3244 set password protocol in kpasswdd [was: LDAP backend support for OpenLDAP 2.1.x]




Here's our patch to do the same thing (you'll also need to modify
k5.asn1 to include ChangePasswdData, I haven't included that here).

I've also included a client-side patch, although I believe that the
latest snapshot of Heimdal includes a third-party patch which does
the same thing.

We suggest that the existing kadm5 ACL mechanism be used rather
than introducing another layer of authorization, at least for
traditional (DB) backends. For directory service backends, such
as LDAP, we added an "impersonate" callback to the HDB interface
that kadm5 may use to operate with least privilege.

regards,

-- Luke

Index: kpasswdd.c
===================================================================
RCS file: /home/project/cvs/heimdal/kpasswd/kpasswdd.c,v
retrieving revision 1.1.1.4
retrieving revision 1.9
diff -u -r1.1.1.4 -r1.9
--- kpasswdd.c	2003/05/28 01:30:54	1.1.1.4
+++ kpasswdd.c	2003/06/06 14:26:39	1.9
@@ -32,7 +32,7 @@
  */
 
 #include "kpasswd_locl.h"
-RCSID("$Id: kpasswdd.c,v 1.1.1.4 2003/05/28 01:30:54 lukeh Exp $");
+RCSID("$Id: kpasswdd.c,v 1.9 2003/06/06 14:26:39 lukeh Exp $");
 
 #include <kadm5/admin.h>
 #ifdef HAVE_SYS_UN_H
@@ -192,6 +192,127 @@
     krb5_data_free (&krb_priv_data);
 }
 
+static void
+change_rfc3244 (krb5_auth_context auth_context,
+	krb5_principal principal,
+	int s,
+	struct sockaddr *sa,
+	int sa_size,
+	krb5_data *pwd_data)
+{
+    krb5_error_code ret;
+    char *client, *target;
+    const char *pwd_reason;
+    kadm5_config_params conf;
+    void *kadm5_handle;
+    char *tmp;
+    ChangePasswdData request;
+    Principal target_princ;
+    krb5_realm r = NULL;
+
+    memset (&conf, 0, sizeof(conf));
+    
+    ret = decode_ChangePasswdData (pwd_data->data,
+	pwd_data->length, &request, NULL);
+    if (ret) {
+	krb5_warn (context, ret, "decode_ChangePasswdData");
+	reply_priv (auth_context, s, sa, sa_size, 2,
+		"Internal error");
+	return;
+    }
+
+    if (request.targname != NULL) {
+        target_princ.name = *(request.targname);
+    }
+    if (request.targrealm != NULL) {
+	target_princ.realm = *(request.targrealm);
+    }
+    if (request.targname != NULL) {
+	if (request.targrealm == NULL) {
+	    ret = krb5_get_default_realm(context, &r);
+	    if (ret) {
+		memset(request.newpasswd.data, 0, request.newpasswd.length);
+		free_ChangePasswdData(&request);
+		krb5_warn (context, ret, "krb5_get_default_realm");
+		reply_priv (auth_context, s, sa, sa_size, 2,
+			"Internal error");
+		return;
+	    }
+	    target_princ.realm = r;
+	}
+	krb5_unparse_name (context, &target_princ, &target);
+    } else {
+	target = NULL;
+    }
+
+    krb5_unparse_name (context, principal, &client);
+
+    ret = kadm5_init_with_password_ctx(context, 
+				       client,
+				       NULL,
+				       KADM5_ADMIN_SERVICE,
+				       &conf, 0, 0, 
+				       &kadm5_handle);
+    if (ret) {
+	free (client);
+	memset(request.newpasswd.data, 0, request.newpasswd.length);
+	free_ChangePasswdData(&request);
+	krb5_warn (context, ret, "kadm5_init_with_password_ctx");
+	reply_priv (auth_context, s, sa, sa_size, 5,
+		    "Access denied");
+	return;
+    }
+
+    if (target != NULL) {
+	krb5_warnx (context, "Changing password of %s for %s", target, client);
+	free (target);
+    } else {
+	krb5_warnx (context, "Changing password for %s", client);
+    }
+    free (client);
+
+    if (request.targname != NULL) {
+	principal = &target_princ;
+    }
+
+    pwd_reason = kadm5_check_password_quality (context, principal, &request.newpasswd);
+    if (pwd_reason != NULL ) {
+	memset(request.newpasswd.data, 0, request.newpasswd.length);
+	free_ChangePasswdData(&request);
+	krb5_warnx (context, "%s", pwd_reason);
+	reply_priv (auth_context, s, sa, sa_size, 4, pwd_reason);
+	kadm5_destroy (kadm5_handle);
+	return;
+    }
+
+    tmp = malloc (request.newpasswd.length + 1);
+    if (tmp == NULL) {
+	krb5_warnx (context, "malloc: out of memory");
+	reply_priv (auth_context, s, sa, sa_size, 2,
+		    "Internal error");
+	goto out;
+    }
+    memcpy (tmp, request.newpasswd.data, request.newpasswd.length);
+    tmp[request.newpasswd.length] = '\0';
+
+    ret = kadm5_s_chpass_principal_cond (kadm5_handle, principal, tmp);
+    memset (tmp, 0, request.newpasswd.length);
+    free (tmp);
+    if (ret) {
+	krb5_warn (context, ret, "kadm5_s_chpass_principal_cond");
+	reply_priv (auth_context, s, sa, sa_size, 2,
+		    "Internal error");
+	goto out;
+    }
+    reply_priv (auth_context, s, sa, sa_size, 0, "Password changed");
+out:
+    kadm5_destroy (kadm5_handle);
+    memset(request.newpasswd.data, 0, request.newpasswd.length);
+    free_ChangePasswdData(&request);
+    if (r != NULL)
+	free (r);
+}
+
 /*
  * Change the password for `principal', sending the reply back on `s'
  * (`sa', `sa_size') to `pwd_data'.
@@ -275,15 +396,16 @@
 	struct sockaddr *sa,
 	int sa_size,
 	u_char *msg,
-	size_t len)
+	size_t len,
+	u_int16_t *pkt_ver)
 {
     krb5_error_code ret;
-    u_int16_t pkt_len, pkt_ver, ap_req_len;
+    u_int16_t pkt_len, ap_req_len;
     krb5_data ap_req_data;
     krb5_data krb_priv_data;
 
     pkt_len = (msg[0] << 8) | (msg[1]);
-    pkt_ver = (msg[2] << 8) | (msg[3]);
+    *pkt_ver = (msg[2] << 8) | (msg[3]);
     ap_req_len = (msg[4] << 8) | (msg[5]);
     if (pkt_len != len) {
 	krb5_warnx (context, "Strange len: %ld != %ld", 
@@ -291,8 +413,9 @@
 	reply_error (server, s, sa, sa_size, 0, 1, "Bad request");
 	return 1;
     }
-    if (pkt_ver != 0x0001) {
-	krb5_warnx (context, "Bad version (%d)", pkt_ver);
+    /* RFC 3244 version is 0xFF80 */
+    if (*pkt_ver != 0x0001 && *pkt_ver != 0xFF80) {
+	krb5_warnx (context, "Bad version (%d)", *pkt_ver);
 	reply_error (server, s, sa, sa_size, 0, 1, "Wrong program version");
 	return 1;
     }
@@ -320,7 +443,7 @@
 	return 1;
     }
 
-    if (!(*ticket)->ticket.flags.initial) {
+    if (*pkt_ver != 0xFF80 && !(*ticket)->ticket.flags.initial) {
 	krb5_warnx (context, "initial flag not set");
 	reply_error (server, s, sa, sa_size, ret, 1,
 		     "Bad request");
@@ -361,6 +484,7 @@
     krb5_data out_data;
     krb5_ticket *ticket;
     krb5_address other_addr;
+    u_int16_t vers;
 
     krb5_data_zero (&out_data);
 
@@ -390,12 +514,20 @@
     }
 
     if (verify (&auth_context, server, keytab, &ticket, &out_data,
-		s, sa, sa_size, msg, len) == 0) {
-	change (auth_context,
-		ticket->client,
-		s,
-		sa, sa_size,
-		&out_data);
+		s, sa, sa_size, msg, len, &vers) == 0) {
+	if (vers == 0xff80) {
+	    change_rfc3244 (auth_context,
+			  ticket->client,
+			  s,
+			  sa, sa_size,
+			  &out_data );
+	} else {
+	    change (auth_context,
+		    ticket->client,
+		    s,
+		    sa, sa_size,
+		    &out_data);
+	}
 	memset (out_data.data, 0, out_data.length);
 	krb5_free_ticket (context, ticket);
 	free (ticket);
@@ -524,6 +656,10 @@
 int version_flag;
 int help_flag;
 char *port_str;
+#ifdef HAVE_DAEMON
+int detach_from_console = 0;
+#define DETACH_IS_DEFAULT FALSE
+#endif
 
 struct getargs args[] = {
 #ifdef HAVE_DLOPEN
@@ -532,6 +668,13 @@
     { "check-function", 0, arg_string, &check_function,
       "password check function to load", "function" },
 #endif
+#ifdef HAVE_DAEMON
+#if DETACH_IS_DEFAULT
+    { "detach", 'D', arg_negative_flag, &detach_from_console, "don't detach from console" },
+#else
+    { "detach",  0, arg_flag, &detach_from_console, "detach from console" },
+#endif
+#endif
     { "keytab", 'k', arg_string, &keytab_str, 
       "keytab to get authentication key from", "kspec" },
     { "realm", 'r', arg_string, &realm_str, "default realm", "realm" },
@@ -606,6 +749,10 @@
     signal(SIGTERM, sigterm);
 #endif
 
+#ifdef HAVE_DAEMON
+    if (detach_from_console)
+	daemon(0, 0);
+#endif
     pidfile(NULL);
 
     return doit (keytab, port);
Index: changepw.c
===================================================================
RCS file: /home/project/cvs/heimdal/lib/krb5/changepw.c,v
retrieving revision 1.1.1.6
retrieving revision 1.2
diff -u -r1.1.1.6 -r1.2
--- changepw.c	2003/05/28 01:31:08	1.1.1.6
+++ changepw.c	2003/06/06 13:58:02	1.2
@@ -33,20 +33,20 @@
 
 #include <krb5_locl.h>
 
-RCSID("$Id: changepw.c,v 1.1.1.6 2003/05/28 01:31:08 lukeh Exp $");
+RCSID("$Id: changepw.c,v 1.2 2003/06/06 13:58:02 lukeh Exp $");
 
 static krb5_error_code
 send_request (krb5_context context,
 	      krb5_auth_context *auth_context,
 	      krb5_creds *creds,
 	      int sock,
-	      char *passwd,
-	      const char *host)
+	      krb5_data *passwd_data,
+	      const char *host,
+	      u_int16_t pkt_ver)
 {
     krb5_error_code ret;
     krb5_data ap_req_data;
     krb5_data krb_priv_data;
-    krb5_data passwd_data;
     size_t len;
     u_char header[6];
     u_char *p;
@@ -64,14 +64,11 @@
     if (ret)
 	return ret;
 
-    passwd_data.data   = passwd;
-    passwd_data.length = strlen(passwd);
-
     krb5_data_zero (&krb_priv_data);
 
     ret = krb5_mk_priv (context,
 			*auth_context,
-			&passwd_data,
+			passwd_data,
 			&krb_priv_data,
 			NULL);
     if (ret)
@@ -81,8 +78,8 @@
     p = header;
     *p++ = (len >> 8) & 0xFF;
     *p++ = (len >> 0) & 0xFF;
-    *p++ = 0;
-    *p++ = 1;
+    *p++ = (pkt_ver >> 8) & 0xFF;
+    *p++ = (pkt_ver >> 0) & 0xFF;
     *p++ = (ap_req_data.length >> 8) & 0xFF;
     *p++ = (ap_req_data.length >> 0) & 0xFF;
 
@@ -259,6 +256,7 @@
     int i;
     int done = 0;
     krb5_realm realm = creds->client->realm;
+    krb5_data passwd_data;
 
     ret = krb5_auth_con_init (context, &auth_context);
     if (ret)
@@ -271,6 +269,9 @@
     if (ret)
 	goto out;
 
+    passwd_data.data   = newpw;
+    passwd_data.length = strlen(newpw);
+
     while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) {
 	struct addrinfo *ai, *a;
 
@@ -308,8 +309,9 @@
 					&auth_context,
 					creds,
 					sock,
-					newpw,
-					hi->hostname);
+					&passwd_data,
+					hi->hostname,
+					1);
 		    if (ret) {
 			close(sock);
 			goto out;
@@ -384,3 +386,182 @@
     else
 	return strings[result];
 }
+
+krb5_error_code
+krb5_set_password (krb5_context	context,
+		   krb5_ccache	ccache,
+		   char		*newpw,
+		   krb5_principal targprinc,
+		   int		*result_code,
+		   krb5_data	*result_code_string,
+		   krb5_data	*result_string)
+{
+    ChangePasswdData request;
+    krb5_error_code ret;
+    krb5_auth_context auth_context;
+    krb5_creds this_cred, *cred;
+    krb5_krbhst_handle handle = NULL;
+    krb5_krbhst_info *hi;
+    krb5_data passwd_data;
+    int sock;
+    int i;
+    int done = 0;
+    krb5_realm realm;
+
+    memset(&request, 0, sizeof(request));
+    memset(&this_cred, 0, sizeof(this_cred));
+    memset(&passwd_data, 0, sizeof(passwd_data));
+
+    ret = krb5_cc_get_principal(context, ccache, &this_cred.client);
+    if (ret != 0) {
+	return ret;
+    }
+
+    realm = (targprinc == NULL) ? targprinc->realm : this_cred.client->realm;
+
+    ret = krb5_parse_name(context, "kadmin/changepw", &this_cred.server);
+    if (ret != 0) {
+	krb5_free_creds_contents(context, &this_cred);
+	return ret;
+    }
+    krb5_princ_set_realm(context, this_cred.server, &realm);
+
+    ret = krb5_get_credentials(context, 0, ccache, &this_cred, &cred);
+    if (ret != 0) {
+	krb5_free_creds_contents(context, &this_cred);
+	return ret;
+    }
+
+    request.newpasswd.length = strlen(newpw);
+    request.newpasswd.data = newpw;
+    if (targprinc != NULL) {
+	request.targname = &targprinc->name;
+	request.targrealm = &targprinc->realm;
+    }
+    ASN1_MALLOC_ENCODE(ChangePasswdData,
+		       passwd_data.data,
+		       passwd_data.length,
+		       &request,
+		       &passwd_data.length,
+		       ret);
+    if (ret != 0) {
+	krb5_free_creds(context, cred);
+	return ret;
+    }
+
+    ret = krb5_krbhst_init(context, realm, KRB5_KRBHST_CHANGEPW, &handle);
+    if (ret != 0) {
+	krb5_free_creds(context, cred);
+	return ret;
+    }
+
+    ret = krb5_auth_con_init(context, &auth_context);
+    if (ret != 0) {
+	krb5_free_creds(context, cred);
+	return ret;
+    }
+    krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
+
+    while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) {
+	struct addrinfo *ai, *a;
+
+	ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
+	if (ret)
+	    continue;
+
+	for (a = ai; !done && a != NULL; a = a->ai_next) {
+	    int replied = 0;
+
+	    sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
+	    if (sock < 0)
+		continue;
+
+	    ret = connect(sock, a->ai_addr, a->ai_addrlen);
+	    if (ret < 0) {
+		close (sock);
+		goto out;
+	    }
+
+	    ret = krb5_auth_con_genaddrs (context, auth_context, sock,
+					  KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);
+	    if (ret) {
+		close (sock);
+		goto out;
+	    }
+
+	    for (i = 0; !done && i < 5; ++i) {
+		fd_set fdset;
+		struct timeval tv;
+
+		if (!replied) {
+		    replied = 0;
+		    ret = send_request (context,
+					&auth_context,
+					cred,
+					sock,
+					&passwd_data,
+					hi->hostname,
+					0xFF80);
+		    if (ret) {
+			close(sock);
+			goto out;
+		    }
+		}
+	    
+		if (sock >= FD_SETSIZE) {
+		    krb5_set_error_string(context, "fd %d too large", sock);
+		    ret = ERANGE;
+		    close (sock);
+		    goto out;
+		}
+
+		FD_ZERO(&fdset);
+		FD_SET(sock, &fdset);
+		tv.tv_usec = 0;
+		tv.tv_sec  = 1 + (1 << i);
+
+		ret = select (sock + 1, &fdset, NULL, NULL, &tv);
+		if (ret < 0 && errno != EINTR) {
+		    close(sock);
+		    goto out;
+		}
+		if (ret == 1) {
+		    ret = process_reply (context,
+					 auth_context,
+					 sock,
+					 result_code,
+					 result_code_string,
+					 result_string,
+					 hi->hostname);
+		    if (ret == 0)
+			done = 1;
+		    else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL)
+			replied = 1;
+		} else {
+		    ret = KRB5_KDC_UNREACH;
+		}
+	    }
+	    close (sock);
+	}
+    }
+
+ out:
+    krb5_krbhst_free (context, handle);
+    krb5_free_creds(context, cred);
+    krb5_auth_con_free (context, auth_context);
+    if (done) {
+	return 0;
+    } else {
+	if (ret == KRB5_KDC_UNREACH)
+	    krb5_set_error_string(context,
+				  "unable to reach any changepw server "
+				  " in realm %s", realm);
+	return ret;
+    }
+
+    memset(passwd_data.data, 0, passwd_data.length);
+    free(passwd_data.data);
+
+    return ret;
+}
+
--
Luke Howard | PADL Software Pty Ltd | www.padl.com