diff options
| author | Jacques Vidrine <nectar@FreeBSD.org> | 2005-02-24 22:22:53 +0000 | 
|---|---|---|
| committer | Jacques Vidrine <nectar@FreeBSD.org> | 2005-02-24 22:22:53 +0000 | 
| commit | 8d4ba808a53020900c275b6f1cf21fc6e9f3bf36 (patch) | |
| tree | d83f145ef556e56c8369b30056ea9442b0017aeb /crypto/heimdal/lib/krb5/changepw.c | |
| parent | e30f8e817ef8205be41fcd6f358620f34f4330b2 (diff) | |
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 a17bf2b37e67..1c4013b50049 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]; | 
