diff options
Diffstat (limited to 'crypto/heimdal/lib/krb5/changepw.c')
-rw-r--r-- | crypto/heimdal/lib/krb5/changepw.c | 548 |
1 files changed, 488 insertions, 60 deletions
diff --git a/crypto/heimdal/lib/krb5/changepw.c b/crypto/heimdal/lib/krb5/changepw.c index a17bf2b..1c4013b 100644 --- a/crypto/heimdal/lib/krb5/changepw.c +++ b/crypto/heimdal/lib/krb5/changepw.c @@ -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. * @@ -33,15 +33,42 @@ #include <krb5_locl.h> -RCSID("$Id: changepw.c,v 1.38 2002/09/29 11:48:34 joda Exp $"); +RCSID("$Id: changepw.c,v 1.38.2.1 2004/06/21 08:38:10 lha Exp $"); + +static void +str2data (krb5_data *d, + const char *fmt, + ...) __attribute__ ((format (printf, 2, 3))); + +static void +str2data (krb5_data *d, + const char *fmt, + ...) +{ + va_list args; + + va_start(args, fmt); + d->length = vasprintf ((char **)&d->data, fmt, args); + va_end(args); +} + +/* + * Change password protocol defined by + * draft-ietf-cat-kerb-chg-password-02.txt + * + * Share the response part of the protocol with MS set password + * (RFC3244) + */ static krb5_error_code -send_request (krb5_context context, - krb5_auth_context *auth_context, - krb5_creds *creds, - int sock, - char *passwd, - const char *host) +chgpw_send_request (krb5_context context, + krb5_auth_context *auth_context, + krb5_creds *creds, + krb5_principal targprinc, + int is_stream, + int sock, + char *passwd, + const char *host) { krb5_error_code ret; krb5_data ap_req_data; @@ -53,6 +80,13 @@ send_request (krb5_context context, struct iovec iov[3]; struct msghdr msghdr; + if (is_stream) + return KRB5_KPASSWD_MALFORMED; + + if (targprinc && + krb5_principal_compare(context, creds->client, targprinc) != TRUE) + return KRB5_KPASSWD_MALFORMED; + krb5_data_zero (&ap_req_data); ret = krb5_mk_req_extended (context, @@ -114,26 +148,120 @@ out2: return ret; } -static void -str2data (krb5_data *d, - const char *fmt, - ...) __attribute__ ((format (printf, 2, 3))); +/* + * Set password protocol as defined by RFC3244 -- + * Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols + */ -static void -str2data (krb5_data *d, - const char *fmt, - ...) +static krb5_error_code +setpw_send_request (krb5_context context, + krb5_auth_context *auth_context, + krb5_creds *creds, + krb5_principal targprinc, + int is_stream, + int sock, + char *passwd, + const char *host) { - va_list args; + krb5_error_code ret; + krb5_data ap_req_data; + krb5_data krb_priv_data; + krb5_data pwd_data; + ChangePasswdDataMS chpw; + size_t len; + u_char header[4 + 6]; + u_char *p; + struct iovec iov[3]; + struct msghdr msghdr; - va_start(args, fmt); - d->length = vasprintf ((char **)&d->data, fmt, args); - va_end(args); + krb5_data_zero (&ap_req_data); + + ret = krb5_mk_req_extended (context, + auth_context, + AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY, + NULL, /* in_data */ + creds, + &ap_req_data); + if (ret) + return ret; + + chpw.newpasswd.length = strlen(passwd); + chpw.newpasswd.data = passwd; + if (targprinc) { + chpw.targname = &targprinc->name; + chpw.targrealm = &targprinc->realm; + } else { + chpw.targname = NULL; + chpw.targrealm = NULL; + } + + ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length, + &chpw, &len, ret); + if (ret) { + krb5_data_free (&ap_req_data); + return ret; + } + + if(pwd_data.length != len) + krb5_abortx(context, "internal error in ASN.1 encoder"); + + ret = krb5_mk_priv (context, + *auth_context, + &pwd_data, + &krb_priv_data, + NULL); + if (ret) + goto out2; + + len = 6 + ap_req_data.length + krb_priv_data.length; + p = header; + if (is_stream) { + _krb5_put_int(p, len, 4); + p += 4; + } + *p++ = (len >> 8) & 0xFF; + *p++ = (len >> 0) & 0xFF; + *p++ = 0xff; + *p++ = 0x80; + *p++ = (ap_req_data.length >> 8) & 0xFF; + *p++ = (ap_req_data.length >> 0) & 0xFF; + + memset(&msghdr, 0, sizeof(msghdr)); + msghdr.msg_name = NULL; + msghdr.msg_namelen = 0; + msghdr.msg_iov = iov; + msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov); +#if 0 + msghdr.msg_control = NULL; + msghdr.msg_controllen = 0; +#endif + + iov[0].iov_base = (void*)header; + if (is_stream) + iov[0].iov_len = 10; + else + iov[0].iov_len = 6; + iov[1].iov_base = ap_req_data.data; + iov[1].iov_len = ap_req_data.length; + iov[2].iov_base = krb_priv_data.data; + iov[2].iov_len = krb_priv_data.length; + + if (sendmsg (sock, &msghdr, 0) < 0) { + ret = errno; + krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret)); + } + + krb5_data_free (&krb_priv_data); +out2: + krb5_data_free (&ap_req_data); + krb5_data_free (&pwd_data); + return ret; } static krb5_error_code process_reply (krb5_context context, krb5_auth_context auth_context, + int is_stream, int sock, int *result_code, krb5_data *result_code_string, @@ -141,30 +269,101 @@ process_reply (krb5_context context, const char *host) { krb5_error_code ret; - u_char reply[BUFSIZ]; - size_t len; + u_char reply[1024 * 3]; + ssize_t len; u_int16_t pkt_len, pkt_ver; - krb5_data ap_rep_data, priv_data; + krb5_data ap_rep_data; int save_errno; - ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL); - if (ret < 0) { - save_errno = errno; - krb5_set_error_string(context, "recvfrom %s: %s", - host, strerror(save_errno)); - return save_errno; + len = 0; + if (is_stream) { + while (len < sizeof(reply)) { + unsigned long size; + + ret = recvfrom (sock, reply + len, sizeof(reply) - len, + 0, NULL, NULL); + if (ret < 0) { + save_errno = errno; + krb5_set_error_string(context, "recvfrom %s: %s", + host, strerror(save_errno)); + return save_errno; + } else if (ret == 0) { + krb5_set_error_string(context, "recvfrom timeout %s", host); + return 1; + } + len += ret; + if (len < 4) + continue; + _krb5_get_int(reply, &size, 4); + if (size + 4 < len) + continue; + memmove(reply, reply + 4, size); + len = size; + break; + } + if (len == sizeof(reply)) { + krb5_set_error_string(context, "message too large from %s", + host); + return ENOMEM; + } + } else { + ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL); + if (ret < 0) { + save_errno = errno; + krb5_set_error_string(context, "recvfrom %s: %s", + host, strerror(save_errno)); + return save_errno; + } + len = ret; + } + + if (len < 6) { + str2data (result_string, "server %s sent to too short message " + "(%d bytes)", host, len); + *result_code = KRB5_KPASSWD_MALFORMED; + return 0; } - len = ret; pkt_len = (reply[0] << 8) | (reply[1]); pkt_ver = (reply[2] << 8) | (reply[3]); + if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) { + KRB_ERROR error; + size_t size; + u_char *p; + + memset(&error, 0, sizeof(error)); + + ret = decode_KRB_ERROR(reply, len, &error, &size); + if (ret) + return ret; + + if (error.e_data->length < 2) { + str2data(result_string, "server %s sent too short " + "e_data to print anything usable", host); + free_KRB_ERROR(&error); + *result_code = KRB5_KPASSWD_MALFORMED; + return 0; + } + + p = error.e_data->data; + *result_code = (p[0] << 8) | p[1]; + if (error.e_data->length == 2) + str2data(result_string, "server only sent error code"); + else + krb5_data_copy (result_string, + p + 2, + error.e_data->length - 2); + free_KRB_ERROR(&error); + return 0; + } + if (pkt_len != len) { str2data (result_string, "client: wrong len in reply"); *result_code = KRB5_KPASSWD_MALFORMED; return 0; } - if (pkt_ver != 0x0001) { + if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) { str2data (result_string, "client: wrong version number (%d)", pkt_ver); *result_code = KRB5_KPASSWD_MALFORMED; @@ -173,15 +372,21 @@ process_reply (krb5_context context, ap_rep_data.data = reply + 6; ap_rep_data.length = (reply[4] << 8) | (reply[5]); - priv_data.data = (u_char*)ap_rep_data.data + ap_rep_data.length; - priv_data.length = len - ap_rep_data.length - 6; - if ((u_char *)priv_data.data + priv_data.length > reply + len) - return KRB5_KPASSWD_MALFORMED; + if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) { + str2data (result_string, "client: wrong AP len in reply"); + *result_code = KRB5_KPASSWD_MALFORMED; + return 0; + } + if (ap_rep_data.length) { krb5_ap_rep_enc_part *ap_rep; + krb5_data priv_data; u_char *p; + priv_data.data = (u_char*)ap_rep_data.data + ap_rep_data.length; + priv_data.length = len - ap_rep_data.length - 6; + ret = krb5_rd_rep (context, auth_context, &ap_rep_data, @@ -207,13 +412,14 @@ process_reply (krb5_context context, "client: bad length in result"); return 0; } - p = result_code_string->data; + + p = result_code_string->data; - *result_code = (p[0] << 8) | p[1]; - krb5_data_copy (result_string, - (unsigned char*)result_code_string->data + 2, - result_code_string->length - 2); - return 0; + *result_code = (p[0] << 8) | p[1]; + krb5_data_copy (result_string, + (unsigned char*)result_code_string->data + 2, + result_code_string->length - 2); + return 0; } else { KRB_ERROR error; size_t size; @@ -237,19 +443,77 @@ process_reply (krb5_context context, } } + /* * change the password using the credentials in `creds' (for the * principal indicated in them) to `newpw', storing the result of * the operation in `result_*' and an error code or 0. */ -krb5_error_code -krb5_change_password (krb5_context context, +typedef krb5_error_code (*kpwd_send_request) (krb5_context, + krb5_auth_context *, + krb5_creds *, + krb5_principal, + int, + int, + char *, + const char *); +typedef krb5_error_code (*kpwd_process_reply) (krb5_context, + krb5_auth_context, + int, + int, + int *, + krb5_data *, + krb5_data *, + const char *); + +struct kpwd_proc { + const char *name; + int flags; +#define SUPPORT_TCP 1 +#define SUPPORT_UDP 2 + kpwd_send_request send_req; + kpwd_process_reply process_rep; +} procs[] = { + { + "MS set password", + SUPPORT_TCP|SUPPORT_UDP, + setpw_send_request, + process_reply + }, + { + "change password", + SUPPORT_UDP, + chgpw_send_request, + process_reply + }, + { NULL } +}; + +static struct kpwd_proc * +find_chpw_proto(const char *name) +{ + struct kpwd_proc *p; + for (p = procs; p->name != NULL; p++) { + if (strcmp(p->name, name) == 0) + return p; + } + return NULL; +} + +/* + * + */ + +static krb5_error_code +change_password_loop (krb5_context context, krb5_creds *creds, + krb5_principal targprinc, char *newpw, int *result_code, krb5_data *result_code_string, - krb5_data *result_string) + krb5_data *result_string, + struct kpwd_proc *proc) { krb5_error_code ret; krb5_auth_context auth_context = NULL; @@ -273,6 +537,22 @@ krb5_change_password (krb5_context context, while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) { struct addrinfo *ai, *a; + int is_stream; + + switch (hi->proto) { + case KRB5_KRBHST_UDP: + if ((proc->flags & SUPPORT_UDP) == 0) + continue; + is_stream = 0; + break; + case KRB5_KRBHST_TCP: + if ((proc->flags & SUPPORT_TCP) == 0) + continue; + is_stream = 1; + break; + default: + continue; + } ret = krb5_krbhst_get_addrinfo(context, hi, &ai); if (ret) @@ -304,12 +584,15 @@ krb5_change_password (krb5_context context, if (!replied) { replied = 0; - ret = send_request (context, - &auth_context, - creds, - sock, - newpw, - hi->hostname); + + ret = (*proc->send_req) (context, + &auth_context, + creds, + targprinc, + is_stream, + sock, + newpw, + hi->hostname); if (ret) { close(sock); goto out; @@ -334,13 +617,14 @@ krb5_change_password (krb5_context context, goto out; } if (ret == 1) { - ret = process_reply (context, - auth_context, - sock, - result_code, - result_code_string, - result_string, - hi->hostname); + ret = (*proc->process_rep) (context, + auth_context, + is_stream, + 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) @@ -367,7 +651,148 @@ krb5_change_password (krb5_context context, } } -const char * + +/* + * change the password using the credentials in `creds' (for the + * principal indicated in them) to `newpw', storing the result of + * the operation in `result_*' and an error code or 0. + */ + +krb5_error_code +krb5_change_password (krb5_context context, + krb5_creds *creds, + char *newpw, + int *result_code, + krb5_data *result_code_string, + krb5_data *result_string) +{ + struct kpwd_proc *p = find_chpw_proto("change password"); + + *result_code = KRB5_KPASSWD_MALFORMED; + result_code_string->data = result_string->data = NULL; + result_code_string->length = result_string->length = 0; + + if (p == NULL) + return KRB5_KPASSWD_MALFORMED; + + return change_password_loop(context, creds, NULL, newpw, + result_code, result_code_string, + result_string, p); +} + +/* + * + */ + +krb5_error_code +krb5_set_password(krb5_context context, + krb5_creds *creds, + char *newpw, + krb5_principal targprinc, + int *result_code, + krb5_data *result_code_string, + krb5_data *result_string) +{ + krb5_principal principal = NULL; + krb5_error_code ret = 0; + int i; + + *result_code = KRB5_KPASSWD_MALFORMED; + result_code_string->data = result_string->data = NULL; + result_code_string->length = result_string->length = 0; + + if (targprinc == NULL) { + ret = krb5_get_default_principal(context, &principal); + if (ret) + return ret; + } else + principal = targprinc; + + for (i = 0; procs[i].name != NULL; i++) { + *result_code = 0; + ret = change_password_loop(context, creds, targprinc, newpw, + result_code, result_code_string, + result_string, + &procs[i]); + if (ret == 0 && *result_code == 0) + break; + } + + if (targprinc == NULL) + krb5_free_principal(context, principal); + return ret; +} + +/* + * + */ + +krb5_error_code +krb5_set_password_using_ccache(krb5_context context, + krb5_ccache ccache, + char *newpw, + krb5_principal targprinc, + int *result_code, + krb5_data *result_code_string, + krb5_data *result_string) +{ + krb5_creds creds, *credsp; + krb5_error_code ret; + krb5_principal principal = NULL; + + *result_code = KRB5_KPASSWD_MALFORMED; + result_code_string->data = result_string->data = NULL; + result_code_string->length = result_string->length = 0; + + memset(&creds, 0, sizeof(creds)); + + if (targprinc == NULL) { + ret = krb5_cc_get_principal(context, ccache, &principal); + if (ret) + return ret; + } else + principal = targprinc; + + ret = krb5_make_principal(context, &creds.server, + krb5_principal_get_realm(context, principal), + "kadmin", "changepw", NULL); + if (ret) + goto out; + + ret = krb5_cc_get_principal(context, ccache, &creds.client); + if (ret) { + krb5_free_principal(context, creds.server); + goto out; + } + + ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp); + krb5_free_principal(context, creds.server); + krb5_free_principal(context, creds.client); + if (ret) + goto out; + + ret = krb5_set_password(context, + credsp, + newpw, + principal, + result_code, + result_code_string, + result_string); + + krb5_free_creds(context, credsp); + + return ret; + out: + if (targprinc == NULL) + krb5_free_principal(context, principal); + return ret; +} + +/* + * + */ + +const char* krb5_passwd_result_to_string (krb5_context context, int result) { @@ -376,10 +801,13 @@ krb5_passwd_result_to_string (krb5_context context, "Malformed", "Hard error", "Auth error", - "Soft error" + "Soft error" , + "Access denied", + "Bad version", + "Initial flag needed" }; - if (result < 0 || result > KRB5_KPASSWD_SOFTERROR) + if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) return "unknown result code"; else return strings[result]; |