add support for Set password protocol as defined by RFC3244 --
authorLove Hörnquist Åstrand <lha@kth.se>
Sat, 20 Sep 2003 00:08:06 +0000 (00:08 +0000)
committerLove Hörnquist Åstrand <lha@kth.se>
Sat, 20 Sep 2003 00:08:06 +0000 (00:08 +0000)
Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols

git-svn-id: svn://svn.h5l.se/heimdal/trunk/heimdal@12888 ec53bebd-3082-4978-b11e-865c3cabbd6b

kpasswd/kpasswdd.c

index 54f91c326731dd45da2c4a1ef20ec48c2ff267b9..95fa735a5a0a0409bec12ee4e8e504d672f1085a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997-2002 Kungliga Tekniska Högskolan
+ * Copyright (c) 1997-2003 Kungliga Tekniska Högskolan
  * (Royal Institute of Technology, Stockholm, Sweden). 
  * All rights reserved. 
  *
@@ -199,70 +199,188 @@ reply_priv (krb5_auth_context auth_context,
 
 static void
 change (krb5_auth_context auth_context,
-       krb5_principal principal,
+       krb5_principal admin_principal,
+       u_int16_t version,
        int s,
        struct sockaddr *sa,
        int sa_size,
-       krb5_data *pwd_data)
+       krb5_data *in_data)
 {
     krb5_error_code ret;
-    char *client;
+    char *client = NULL, *admin = NULL;
     const char *pwd_reason;
     kadm5_config_params conf;
-    void *kadm5_handle;
+    void *kadm5_handle = NULL;
+    krb5_principal principal;
+    krb5_data *pwd_data = NULL;
     char *tmp;
+    ChangePasswdDataMS chpw;
 
     memset (&conf, 0, sizeof(conf));
+    memset(&chpw, 0, sizeof(chpw));
     
-    krb5_unparse_name (context, principal, &client);
+    if (version == KRB5_KPASSWD_VERS_CHANGEPW) {
+       ret = krb5_copy_data(context, &chpw.newpasswd, &pwd_data);
+       if (ret) {
+           krb5_warn (context, ret, "krb5_copy_data");
+           reply_priv (auth_context, s, sa, sa_size, KRB5_KPASSWD_MALFORMED,
+                       "out out memory copying password");
+           return;
+       }
+       principal = admin_principal;
+    } else if (version == KRB5_KPASSWD_VERS_SETPW) {
+       size_t len;
+
+       ret = decode_ChangePasswdDataMS(in_data->data, in_data->length,
+                                       &chpw, &len);
+       if (ret) {
+           krb5_warn (context, ret, "decode_ChangePasswdDataMS");
+           reply_priv (auth_context, s, sa, sa_size, KRB5_KPASSWD_MALFORMED,
+                       "malformed ChangePasswdData");
+           return;
+       }
+       
+
+       ret = krb5_copy_data(context, &chpw.newpasswd, &pwd_data);
+       if (ret) {
+           krb5_warn (context, ret, "krb5_copy_data");
+           reply_priv (auth_context, s, sa, sa_size, KRB5_KPASSWD_MALFORMED,
+                       "out out memory copying password");
+           goto out;
+       }
+
+       if (chpw.targname == NULL && chpw.targrealm != NULL) {
+           krb5_warn (context, ret, "kadm5_init_with_password_ctx");
+           reply_priv (auth_context, s, sa, sa_size, 
+                       KRB5_KPASSWD_MALFORMED,
+                       "targrealm but not targname");
+           goto out;
+       }
+
+       if (chpw.targname) {
+           krb5_principal_data princ;
+
+           princ.name = *chpw.targname;
+           princ.realm = *chpw.targrealm;
+           if (princ.realm == NULL) {
+               ret = krb5_get_default_realm(context, &princ.realm);
+
+               if (ret) {
+                   krb5_warnx (context, 
+                               "kadm5_init_with_password_ctx: "
+                               "failed to allocate realm");
+                   reply_priv (auth_context, s, sa, sa_size, 
+                               KRB5_KPASSWD_SOFTERROR,
+                               "failed to allocate realm");
+                   goto out;
+               }
+           }
+           ret = krb5_copy_principal(context, &princ, &principal);
+           if (ret)
+               abort();
+           if (*chpw.targrealm == NULL)
+               free(princ.realm);
+       } else
+           principal = admin_principal;
+    } else {
+       krb5_warnx (context, "kadm5_init_with_password_ctx: unknown proto");
+       reply_priv (auth_context, s, sa, sa_size, 
+                   KRB5_KPASSWD_HARDERROR,
+                   "Unknown protocol used");
+       return;
+    }
+
+    ret = krb5_unparse_name (context, admin_principal, &admin);
+    if (ret) {
+       krb5_warn (context, ret, "unparse_name failed");
+       reply_priv (auth_context, s, sa, sa_size, 
+                   KRB5_KPASSWD_HARDERROR, "out of memory error");
+       goto out;
+    }
 
     ret = kadm5_init_with_password_ctx(context, 
-                                      client,
+                                      admin,
                                       NULL,
                                       KADM5_ADMIN_SERVICE,
                                       &conf, 0, 0, 
                                       &kadm5_handle);
     if (ret) {
-       free (client);
        krb5_warn (context, ret, "kadm5_init_with_password_ctx");
        reply_priv (auth_context, s, sa, sa_size, 2,
                    "Internal error");
-       return;
+       goto out;
     }
 
-    krb5_warnx (context, "Changing password for %s", client);
-    free (client);
+    ret = krb5_unparse_name(context, principal, &client);
+    if (ret) {
+       krb5_warn (context, ret, "unparse_name failed");
+       reply_priv (auth_context, s, sa, sa_size, 
+                   KRB5_KPASSWD_HARDERROR, "out of memory error");
+       goto out;
+    }
 
-    pwd_reason = kadm5_check_password_quality (context, principal, pwd_data);
-    if (pwd_reason != NULL ) {
-       krb5_warnx (context, "%s", pwd_reason);
-       reply_priv (auth_context, s, sa, sa_size, 4, pwd_reason);
-       kadm5_destroy (kadm5_handle);
-       return;
+    /*
+     * Check password quality if not changing as administrator
+     */
+
+    if (krb5_principal_compare(context, admin_principal, principal) == TRUE) {
+
+       pwd_reason = kadm5_check_password_quality (context, principal, 
+                                                  pwd_data);
+       if (pwd_reason != NULL ) {
+           krb5_warnx (context, 
+                       "%s didn't pass password quality check with error: %s",
+                       client, pwd_reason);
+           reply_priv (auth_context, s, sa, sa_size, 
+                       KRB5_KPASSWD_SOFTERROR, pwd_reason);
+           goto out;
+       }
+       krb5_warnx (context, "Changing password for %s", client);
+    } else {
+       ret = _kadm5_acl_check_permission(kadm5_handle, KADM5_PRIV_CPW, 
+                                         principal);
+       if (ret) {
+           krb5_warn (context, ret, 
+                      "Check ACL failed for %s for changing %s password",
+                      admin, client);
+           reply_priv (auth_context, s, sa, sa_size, 
+                       KRB5_KPASSWD_HARDERROR, "permission denied");
+           goto out;
+       }
+       krb5_warnx (context, "%s is changing password for %s", admin, client);
     }
 
-    tmp = malloc (pwd_data->length + 1);
-    if (tmp == NULL) {
-       krb5_warnx (context, "malloc: out of memory");
-       reply_priv (auth_context, s, sa, sa_size, 2,
+    ret = krb5_data_realloc(pwd_data, pwd_data->length + 1);
+    if (ret) {
+       krb5_warn (context, ret, "malloc: out of memory");
+       reply_priv (auth_context, s, sa, sa_size, KRB5_KPASSWD_HARDERROR,
                    "Internal error");
        goto out;
     }
-    memcpy (tmp, pwd_data->data, pwd_data->length);
-    tmp[pwd_data->length] = '\0';
+    tmp = pwd_data->data;
+    tmp[pwd_data->length - 1] = '\0';
 
     ret = kadm5_s_chpass_principal_cond (kadm5_handle, principal, tmp);
-    memset (tmp, 0, pwd_data->length);
-    free (tmp);
+    krb5_free_data (context, pwd_data);
+    pwd_data = NULL;
     if (ret) {
        krb5_warn (context, ret, "kadm5_s_chpass_principal_cond");
-       reply_priv (auth_context, s, sa, sa_size, 2,
+       reply_priv (auth_context, s, sa, sa_size, KRB5_KPASSWD_HARDERROR,
                    "Internal error");
        goto out;
     }
-    reply_priv (auth_context, s, sa, sa_size, 0, "Password changed");
+    reply_priv (auth_context, s, sa, sa_size, KRB5_KPASSWD_SUCCESS,
+               "Password changed");
 out:
-    kadm5_destroy (kadm5_handle);
+    free_ChangePasswdDataMS(&chpw);
+    if (admin)
+       free(admin);
+    if (client)
+       free(client);
+    if (pwd_data)
+       krb5_free_data(context, pwd_data);
+    if (kadm5_handle)
+       kadm5_destroy (kadm5_handle);
 }
 
 static int
@@ -271,6 +389,7 @@ verify (krb5_auth_context *auth_context,
        krb5_keytab keytab,
        krb5_ticket **ticket,
        krb5_data *out_data,
+       u_int16_t *version,
        int s,
        struct sockaddr *sa,
        int sa_size,
@@ -291,11 +410,13 @@ verify (krb5_auth_context *auth_context,
        reply_error (server, s, sa, sa_size, 0, 1, "Bad request");
        return 1;
     }
-    if (pkt_ver != 0x0001) {
+    if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW &&
+       pkt_ver != KRB5_KPASSWD_VERS_SETPW) {
        krb5_warnx (context, "Bad version (%d)", pkt_ver);
        reply_error (server, s, sa, sa_size, 0, 1, "Wrong program version");
        return 1;
     }
+    *version = pkt_ver;
 
     ap_req_data.data   = msg + 6;
     ap_req_data.length = ap_req_len;
@@ -361,6 +482,8 @@ process (krb5_principal server,
     krb5_data out_data;
     krb5_ticket *ticket;
     krb5_address other_addr;
+    u_int16_t version;
+
 
     krb5_data_zero (&out_data);
 
@@ -390,9 +513,10 @@ process (krb5_principal server,
     }
 
     if (verify (&auth_context, server, keytab, &ticket, &out_data,
-               s, sa, sa_size, msg, len) == 0) {
+               &version, s, sa, sa_size, msg, len) == 0) {
        change (auth_context,
                ticket->client,
+               version,
                s,
                sa, sa_size,
                &out_data);