diff options
Diffstat (limited to 'src/kadmin/server/schpw.c')
| -rw-r--r-- | src/kadmin/server/schpw.c | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/src/kadmin/server/schpw.c b/src/kadmin/server/schpw.c new file mode 100644 index 000000000000..900adf7a0997 --- /dev/null +++ b/src/kadmin/server/schpw.c @@ -0,0 +1,487 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +#include "k5-int.h" +#include <kadm5/admin.h> +#include <syslog.h> +#include <adm_proto.h> /* krb5_klog_syslog */ +#include <stdio.h> +#include <errno.h> + +#include "kadm5/server_internal.h" /* XXX for kadm5_server_handle_t */ + +#include "misc.h" + +#ifndef GETSOCKNAME_ARG3_TYPE +#define GETSOCKNAME_ARG3_TYPE int +#endif + +#define RFC3244_VERSION 0xff80 + +static krb5_error_code +process_chpw_request(krb5_context context, void *server_handle, char *realm, + krb5_keytab keytab, const krb5_fulladdr *local_faddr, + const krb5_fulladdr *remote_faddr, krb5_data *req, + krb5_data *rep) +{ + krb5_error_code ret; + char *ptr; + unsigned int plen, vno; + krb5_data ap_req, ap_rep = empty_data(); + krb5_data cipher = empty_data(), clear = empty_data(); + krb5_auth_context auth_context = NULL; + krb5_principal changepw = NULL; + krb5_principal client, target = NULL; + krb5_ticket *ticket = NULL; + krb5_replay_data replay; + krb5_error krberror; + int numresult; + char strresult[1024]; + char *clientstr = NULL, *targetstr = NULL; + const char *errmsg = NULL; + size_t clen; + char *cdots; + struct sockaddr_storage ss; + socklen_t salen; + char addrbuf[100]; + krb5_address *addr = remote_faddr->address; + + *rep = empty_data(); + + 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; + strlcpy(strresult, "Request was truncated", sizeof(strresult)); + goto bailout; + } + + ptr = req->data; + + /* verify length */ + + plen = (*ptr++ & 0xff); + plen = (plen<<8) | (*ptr++ & 0xff); + + if (plen != req->length) { + ret = KRB5KRB_AP_ERR_MODIFIED; + numresult = KRB5_KPASSWD_MALFORMED; + strlcpy(strresult, "Request length was inconsistent", + sizeof(strresult)); + goto bailout; + } + + /* verify version number */ + + vno = (*ptr++ & 0xff) ; + vno = (vno<<8) | (*ptr++ & 0xff); + + if (vno != 1 && vno != RFC3244_VERSION) { + ret = KRB5KDC_ERR_BAD_PVNO; + numresult = KRB5_KPASSWD_BAD_VERSION; + snprintf(strresult, sizeof(strresult), + "Request contained unknown protocol version number %d", vno); + goto bailout; + } + + /* 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; + strlcpy(strresult, "Request was truncated in AP-REQ", + sizeof(strresult)); + goto bailout; + } + + /* verify ap_req */ + + ap_req.data = ptr; + ptr += ap_req.length; + + ret = krb5_auth_con_init(context, &auth_context); + if (ret) { + numresult = KRB5_KPASSWD_HARDERROR; + strlcpy(strresult, "Failed initializing auth context", + sizeof(strresult)); + goto chpwfail; + } + + ret = krb5_auth_con_setflags(context, auth_context, + KRB5_AUTH_CONTEXT_DO_SEQUENCE); + if (ret) { + numresult = KRB5_KPASSWD_HARDERROR; + strlcpy(strresult, "Failed initializing auth context", + sizeof(strresult)); + goto chpwfail; + } + + ret = krb5_build_principal(context, &changepw, strlen(realm), realm, + "kadmin", "changepw", NULL); + if (ret) { + numresult = KRB5_KPASSWD_HARDERROR; + strlcpy(strresult, "Failed building kadmin/changepw principal", + sizeof(strresult)); + goto chpwfail; + } + + ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab, + NULL, &ticket); + + if (ret) { + numresult = KRB5_KPASSWD_AUTHERROR; + strlcpy(strresult, "Failed reading application request", + sizeof(strresult)); + goto chpwfail; + } + + /* construct the ap-rep */ + + ret = krb5_mk_rep(context, auth_context, &ap_rep); + if (ret) { + numresult = KRB5_KPASSWD_AUTHERROR; + strlcpy(strresult, "Failed replying to application request", + sizeof(strresult)); + goto chpwfail; + } + + /* decrypt the ChangePasswdData */ + + cipher.length = (req->data + req->length) - ptr; + cipher.data = ptr; + + /* + * Don't set a remote address in auth_context before calling krb5_rd_priv, + * so that we can work against clients behind a NAT. Reflection attacks + * aren't a concern since we use sequence numbers and since our requests + * don't look anything like our responses. Also don't set a local address, + * since we don't know what interface the request was received on. + */ + + ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay); + if (ret) { + numresult = KRB5_KPASSWD_HARDERROR; + strlcpy(strresult, "Failed decrypting request", sizeof(strresult)); + goto chpwfail; + } + + client = ticket->enc_part2->client; + + /* decode ChangePasswdData for setpw requests */ + if (vno == RFC3244_VERSION) { + krb5_data *clear_data; + + ret = decode_krb5_setpw_req(&clear, &clear_data, &target); + if (ret != 0) { + numresult = KRB5_KPASSWD_MALFORMED; + strlcpy(strresult, "Failed decoding ChangePasswdData", + sizeof(strresult)); + goto chpwfail; + } + + zapfree(clear.data, clear.length); + + clear = *clear_data; + free(clear_data); + + if (target != NULL) { + ret = krb5_unparse_name(context, target, &targetstr); + if (ret != 0) { + numresult = KRB5_KPASSWD_HARDERROR; + strlcpy(strresult, "Failed unparsing target name for log", + sizeof(strresult)); + goto chpwfail; + } + } + } + + ret = krb5_unparse_name(context, client, &clientstr); + if (ret) { + numresult = KRB5_KPASSWD_HARDERROR; + strlcpy(strresult, "Failed unparsing client name for log", + sizeof(strresult)); + goto chpwfail; + } + + /* for cpw, verify that this is an AS_REQ ticket */ + if (vno == 1 && + (ticket->enc_part2->flags & TKT_FLG_INITIAL) == 0) { + numresult = KRB5_KPASSWD_INITIAL_FLAG_NEEDED; + strlcpy(strresult, "Ticket must be derived from a password", + sizeof(strresult)); + goto chpwfail; + } + + /* change the password */ + + ptr = k5memdup0(clear.data, clear.length, &ret); + ret = schpw_util_wrapper(server_handle, client, target, + (ticket->enc_part2->flags & TKT_FLG_INITIAL) != 0, + ptr, NULL, strresult, sizeof(strresult)); + if (ret) + errmsg = krb5_get_error_message(context, ret); + + /* zap the password */ + zapfree(clear.data, clear.length); + zapfree(ptr, clear.length); + clear = empty_data(); + + clen = strlen(clientstr); + trunc_name(&clen, &cdots); + + switch (addr->addrtype) { + case ADDRTYPE_INET: { + struct sockaddr_in *sin = ss2sin(&ss); + + sin->sin_family = AF_INET; + memcpy(&sin->sin_addr, addr->contents, addr->length); + sin->sin_port = htons(remote_faddr->port); + salen = sizeof(*sin); + break; + } + case ADDRTYPE_INET6: { + struct sockaddr_in6 *sin6 = ss2sin6(&ss); + + sin6->sin6_family = AF_INET6; + memcpy(&sin6->sin6_addr, addr->contents, addr->length); + sin6->sin6_port = htons(remote_faddr->port); + salen = sizeof(*sin6); + break; + } + default: { + struct sockaddr *sa = ss2sa(&ss); + + sa->sa_family = AF_UNSPEC; + salen = sizeof(*sa); + break; + } + } + + if (getnameinfo(ss2sa(&ss), salen, + addrbuf, sizeof(addrbuf), NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + strlcpy(addrbuf, "<unprintable>", sizeof(addrbuf)); + + if (vno == RFC3244_VERSION) { + size_t tlen; + char *tdots; + const char *targetp; + + if (target == NULL) { + tlen = clen; + tdots = cdots; + targetp = targetstr; + } else { + tlen = strlen(targetstr); + trunc_name(&tlen, &tdots); + targetp = clientstr; + } + + krb5_klog_syslog(LOG_NOTICE, _("setpw request from %s by %.*s%s for " + "%.*s%s: %s"), addrbuf, (int) clen, + clientstr, cdots, (int) tlen, targetp, tdots, + errmsg ? errmsg : "success"); + } else { + krb5_klog_syslog(LOG_NOTICE, _("chpw request from %s for %.*s%s: %s"), + addrbuf, (int) clen, clientstr, cdots, + errmsg ? errmsg : "success"); + } + switch (ret) { + case KADM5_AUTH_CHANGEPW: + numresult = KRB5_KPASSWD_ACCESSDENIED; + break; + case KADM5_PASS_Q_TOOSHORT: + case KADM5_PASS_REUSE: + case KADM5_PASS_Q_CLASS: + case KADM5_PASS_Q_DICT: + case KADM5_PASS_Q_GENERIC: + case KADM5_PASS_TOOSOON: + numresult = KRB5_KPASSWD_SOFTERROR; + break; + case 0: + numresult = KRB5_KPASSWD_SUCCESS; + strlcpy(strresult, "", sizeof(strresult)); + break; + default: + numresult = KRB5_KPASSWD_HARDERROR; + break; + } + +chpwfail: + + ret = alloc_data(&clear, 2 + strlen(strresult)); + if (ret) + goto bailout; + + ptr = clear.data; + + *ptr++ = (numresult>>8) & 0xff; + *ptr++ = numresult & 0xff; + + memcpy(ptr, strresult, strlen(strresult)); + + cipher = empty_data(); + + if (ap_rep.length) { + ret = krb5_auth_con_setaddrs(context, auth_context, + local_faddr->address, NULL); + if (ret) { + numresult = KRB5_KPASSWD_HARDERROR; + strlcpy(strresult, + "Failed storing client and server internet addresses", + sizeof(strresult)); + } else { + ret = krb5_mk_priv(context, auth_context, &clear, &cipher, + &replay); + if (ret) { + numresult = KRB5_KPASSWD_HARDERROR; + strlcpy(strresult, "Failed encrypting reply", + sizeof(strresult)); + } + } + } + + /* 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) { + free(ap_rep.data); + ap_rep = empty_data(); + } + + krberror.ctime = 0; + krberror.cusec = 0; + krberror.susec = 0; + ret = krb5_timeofday(context, &krberror.stime); + if (ret) + 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 > KRB_ERR_MAX) + krberror.error = KRB_ERR_GENERIC; + + krberror.client = NULL; + + ret = krb5_build_principal(context, &krberror.server, + strlen(realm), realm, + "kadmin", "changepw", NULL); + if (ret) + 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 */ + + ret = alloc_data(rep, 6 + ap_rep.length + cipher.length); + if (ret) + goto bailout; + 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: + krb5_auth_con_free(context, auth_context); + krb5_free_principal(context, changepw); + krb5_free_ticket(context, ticket); + free(ap_rep.data); + free(clear.data); + free(cipher.data); + krb5_free_principal(context, target); + krb5_free_unparsed_name(context, targetstr); + krb5_free_unparsed_name(context, clientstr); + krb5_free_error_message(context, errmsg); + return ret; +} + +/* Dispatch routine for set/change password */ +void +dispatch(void *handle, struct sockaddr *local_saddr, + const krb5_fulladdr *remote_faddr, krb5_data *request, int is_tcp, + verto_ctx *vctx, loop_respond_fn respond, void *arg) +{ + krb5_error_code ret; + krb5_keytab kt = NULL; + kadm5_server_handle_t server_handle = (kadm5_server_handle_t)handle; + krb5_fulladdr local_faddr; + krb5_address **local_kaddrs = NULL, local_kaddr_buf; + krb5_data *response = NULL; + + if (local_saddr == NULL) { + ret = krb5_os_localaddr(server_handle->context, &local_kaddrs); + if (ret != 0) + goto egress; + + local_faddr.address = local_kaddrs[0]; + local_faddr.port = 0; + } else { + local_faddr.address = &local_kaddr_buf; + init_addr(&local_faddr, local_saddr); + } + + ret = krb5_kt_resolve(server_handle->context, "KDB:", &kt); + if (ret != 0) { + krb5_klog_syslog(LOG_ERR, _("chpw: Couldn't open admin keytab %s"), + krb5_get_error_message(server_handle->context, ret)); + goto egress; + } + + response = k5alloc(sizeof(krb5_data), &ret); + if (response == NULL) + goto egress; + + ret = process_chpw_request(server_handle->context, + handle, + server_handle->params.realm, + kt, + &local_faddr, + remote_faddr, + request, + response); +egress: + if (ret) + krb5_free_data(server_handle->context, response); + krb5_free_addresses(server_handle->context, local_kaddrs); + krb5_kt_close(server_handle->context, kt); + (*respond)(arg, ret, ret == 0 ? response : NULL); +} |
