summaryrefslogtreecommitdiff
path: root/src/kadmin/server/schpw.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kadmin/server/schpw.c')
-rw-r--r--src/kadmin/server/schpw.c487
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);
+}