From: Tom Yu Date: Wed, 21 Jan 1998 05:20:41 +0000 (+0000) Subject: * schpw.c: New file. Support for Cygnus chpw. X-Git-Tag: krb5-1.1-beta1~891 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=43817ef775a04fb5694c39d9d6a27587ce2720a4;p=krb5.git * schpw.c: New file. Support for Cygnus chpw. [oops forgot this earlier] git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@10359 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/src/kadmin/server/schpw.c b/src/kadmin/server/schpw.c new file mode 100644 index 000000000..cb4a5de0d --- /dev/null +++ b/src/kadmin/server/schpw.c @@ -0,0 +1,372 @@ +#define NEED_SOCKETS +#include "k5-int.h" +#include + +#include +#include + +krb5_error_code +process_chpw_request(context, server_handle, realm, s, keytab, sin, req, rep) + krb5_context context; + void *server_handle; + char *realm; + int s; + krb5_keytab keytab; + struct sockaddr_in *sin; + krb5_data *req; + krb5_data *rep; +{ + krb5_error_code ret; + char *ptr; + int plen, vno; + krb5_address local_kaddr, remote_kaddr; + krb5_data ap_req, ap_rep; + krb5_auth_context auth_context; + krb5_principal changepw; + krb5_ticket *ticket; + krb5_data cipher, clear; + struct sockaddr local_addr, remote_addr; + int addrlen; + krb5_replay_data replay; + krb5_error krberror; + int numresult; + char strresult[1024]; + + ret = 0; + rep->length = 0; + + auth_context = NULL; + changepw = NULL; + ap_rep.length = 0; + ticket = NULL; + clear.length = 0; + cipher.length = 0; + + if (req->length < 4) { + /* either this, or the server is printing bad messages, + or the caller passed in garbage */ + ret = KRB5KRB_AP_ERR_MODIFIED; + numresult = KRB5_KPASSWD_MALFORMED; + strcpy(strresult, "Request was truncated"); + goto chpwfail; + } + + ptr = req->data; + + /* verify length */ + + plen = (*ptr++ & 0xff); + plen = (plen<<8) | (*ptr++ & 0xff); + + if (plen != req->length) + return(KRB5KRB_AP_ERR_MODIFIED); + + /* verify version number */ + + vno = (*ptr++ & 0xff) ; + vno = (vno<<8) | (*ptr++ & 0xff); + + if (vno != 1) { + ret = KRB5KDC_ERR_BAD_PVNO; + numresult = KRB5_KPASSWD_MALFORMED; + sprintf(strresult, + "Request contained unknown protocol version number %d", vno); + goto chpwfail; + } + + /* read, check ap-req length */ + + ap_req.length = (*ptr++ & 0xff); + ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff); + + if (ptr + ap_req.length >= req->data + req->length) { + ret = KRB5KRB_AP_ERR_MODIFIED; + numresult = KRB5_KPASSWD_MALFORMED; + strcpy(strresult, "Request was truncated in AP-REQ"); + goto chpwfail; + } + + /* verify ap_req */ + + ap_req.data = ptr; + ptr += ap_req.length; + + if (ret = krb5_auth_con_init(context, &auth_context)) { + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, "Failed initializing auth context"); + goto chpwfail; + } + + if (ret = krb5_auth_con_setflags(context, auth_context, + KRB5_AUTH_CONTEXT_DO_SEQUENCE)) { + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, "Failed initializing auth context"); + goto chpwfail; + } + + if (ret = krb5_build_principal(context, &changepw, strlen(realm), realm, + "kadmin", "changepw", NULL)) { + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, "Failed building kadmin/changepw principal"); + goto chpwfail; + } + + ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab, + NULL, &ticket); + + if (ret) { + numresult = KRB5_KPASSWD_AUTHERROR; + strcpy(strresult, "Failed reading application request"); + goto chpwfail; + } + + /* set up address info */ + + addrlen = sizeof(local_addr); + + if (getsockname(s, &local_addr, &addrlen) < 0) { + ret = errno; + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, "Failed getting server internet address"); + goto chpwfail; + } + + /* some brain-dead OS's don't return useful information from + * the getsockname call. Namely, windows and solaris. */ + + if (((struct sockaddr_in *)&local_addr)->sin_addr.s_addr != 0) { + local_kaddr.addrtype = ADDRTYPE_INET; + local_kaddr.length = + sizeof(((struct sockaddr_in *) &local_addr)->sin_addr); + local_kaddr.contents = + (char *) &(((struct sockaddr_in *) &local_addr)->sin_addr); + } else { + krb5_address **addrs; + + krb5_os_localaddr(context, &addrs); + local_kaddr.magic = addrs[0]->magic; + local_kaddr.addrtype = addrs[0]->addrtype; + local_kaddr.length = addrs[0]->length; + local_kaddr.contents = malloc(addrs[0]->length); + memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length); + + krb5_free_addresses(context, addrs); + } + + addrlen = sizeof(remote_addr); + + if (getpeername(s, &remote_addr, &addrlen) < 0) { + ret = errno; + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, "Failed getting client internet address"); + goto chpwfail; + } + + remote_kaddr.addrtype = ADDRTYPE_INET; + remote_kaddr.length = + sizeof(((struct sockaddr_in *) &remote_addr)->sin_addr); + remote_kaddr.contents = + (char *) &(((struct sockaddr_in *) &remote_addr)->sin_addr); + + remote_kaddr.addrtype = ADDRTYPE_INET; + remote_kaddr.length = sizeof(sin->sin_addr); + remote_kaddr.contents = (char *) &sin->sin_addr; + + /* mk_priv requires that the local address be set. + getsockname is used for this. rd_priv requires that the + remote address be set. recvfrom is used for this. If + rd_priv is given a local address, and the message has the + recipient addr in it, this will be checked. However, there + is simply no way to know ahead of time what address the + message will be delivered *to*. Therefore, it is important + that either no recipient address is in the messages when + mk_priv is called, or that no local address is passed to + rd_priv. Both is a better idea, and I have done that. In + summary, when mk_priv is called, *only* a local address is + specified. when rd_priv is called, *only* a remote address + is specified. Are we having fun yet? */ + + if (ret = krb5_auth_con_setaddrs(context, auth_context, NULL, + &remote_kaddr)) { + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, "Failed storing client internet address"); + goto chpwfail; + } + + /* verify that this is an AS_REQ ticket */ + + if (!(ticket->enc_part2->flags & TKT_FLG_INITIAL)) { + numresult = KRB5_KPASSWD_AUTHERROR; + strcpy(strresult, "Ticket must be derived from a password"); + goto chpwfail; + } + + /* construct the ap-rep */ + + if (ret = krb5_mk_rep(context, auth_context, &ap_rep)) { + numresult = KRB5_KPASSWD_AUTHERROR; + strcpy(strresult, "Failed replying to application request"); + goto chpwfail; + } + + /* decrypt the new password */ + + cipher.length = (req->data + req->length) - ptr; + cipher.data = ptr; + + if (ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay)) { + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, "Failed decrypting request"); + goto chpwfail; + } + + /* change the password */ + + ptr = (char *) malloc(clear.length+1); + memcpy(ptr, clear.data, clear.length); + ptr[clear.length] = '\0'; + + ret = kadm5_chpass_principal_util(server_handle, ticket->enc_part2->client, + ptr, NULL, strresult); + + /* zap the password */ + memset(clear.data, 0, clear.length); + memset(ptr, 0, clear.length); + krb5_xfree(clear.data); + free(ptr); + clear.length = 0; + + if (ret) { + if ((ret != KADM5_PASS_Q_TOOSHORT) && + (ret != KADM5_PASS_REUSE) && (ret != KADM5_PASS_Q_CLASS) && + (ret != KADM5_PASS_Q_DICT) && (ret != KADM5_PASS_TOOSOON)) + numresult = KRB5_KPASSWD_HARDERROR; + else + numresult = KRB5_KPASSWD_SOFTERROR; + /* strresult set by kadb5_chpass_principal_util() */ + goto chpwfail; + } + + /* success! */ + + numresult = KRB5_KPASSWD_SUCCESS; + strcpy(strresult, ""); + +chpwfail: + + clear.length = 2 + strlen(strresult); + clear.data = (char *) malloc(clear.length); + + ptr = clear.data; + + *ptr++ = (numresult>>8) & 0xff; + *ptr++ = numresult & 0xff; + + memcpy(ptr, strresult, strlen(strresult)); + + cipher.length = 0; + + if (ap_rep.length) { + if (ret = krb5_auth_con_setaddrs(context, auth_context, &local_kaddr, + NULL)) { + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, + "Failed storing client and server internet addresses"); + } else { + if (ret = krb5_mk_priv(context, auth_context, &clear, &cipher, + &replay)) { + numresult = KRB5_KPASSWD_HARDERROR; + strcpy(strresult, "Failed encrypting reply"); + } + } + } + + /* if no KRB-PRIV was constructed, then we need a KRB-ERROR. + if this fails, just bail. there's nothing else we can do. */ + + if (cipher.length == 0) { + /* clear out ap_rep now, so that it won't be inserted in the + reply */ + + if (ap_rep.length) { + krb5_xfree(ap_rep.data); + ap_rep.length = 0; + } + + krberror.ctime = 0; + krberror.cusec = 0; + krberror.susec = 0; + if (ret = krb5_timeofday(context, &krberror.stime)) + goto bailout; + + /* this is really icky. but it's what all the other callers + to mk_error do. */ + krberror.error = ret; + krberror.error -= ERROR_TABLE_BASE_krb5; + if (krberror.error < 0 || krberror.error > 128) + krberror.error = KRB_ERR_GENERIC; + + krberror.client = NULL; + if (ret = krb5_build_principal(context, &krberror.server, + strlen(realm), realm, + "kadmin", "changepw", NULL)) + goto bailout; + krberror.text.length = 0; + krberror.e_data = clear; + + ret = krb5_mk_error(context, &krberror, &cipher); + + krb5_free_principal(context, krberror.server); + + if (ret) + goto bailout; + } + + /* construct the reply */ + + rep->length = 6 + ap_rep.length + cipher.length; + rep->data = (char *) malloc(rep->length); + ptr = rep->data; + + /* length */ + + *ptr++ = (rep->length>>8) & 0xff; + *ptr++ = rep->length & 0xff; + + /* version == 0x0001 big-endian */ + + *ptr++ = 0; + *ptr++ = 1; + + /* ap_rep length, big-endian */ + + *ptr++ = (ap_rep.length>>8) & 0xff; + *ptr++ = ap_rep.length & 0xff; + + /* ap-rep data */ + + if (ap_rep.length) { + memcpy(ptr, ap_rep.data, ap_rep.length); + ptr += ap_rep.length; + } + + /* krb-priv or krb-error */ + + memcpy(ptr, cipher.data, cipher.length); + +bailout: + if (auth_context) + krb5_auth_con_free(context, auth_context); + if (changepw) + krb5_free_principal(context, changepw); + if (ap_rep.length) + krb5_xfree(ap_rep.data); + if (ticket) + krb5_free_ticket(context, ticket); + if (clear.length) + krb5_xfree(clear.data); + if (cipher.length) + krb5_xfree(cipher.data); + + return(ret); +}