From 888bc144da94c9bf8d2c35ab38868e748c059de3 Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Fri, 5 Sep 2014 17:51:35 -0400 Subject: Add HTTPS patches from master Pull in a stack of patches to add support for accessing servers via HTTPS proxies, such as python-kdcproxy or the KDC Proxy Service on a properly-outfitted Windows box. Pull in the patch to move the logic out of libkrb5 proper and into a loadable plugin to avoid linking our local applications against our libkrb5 against libssl against the installed copy of libgssapi_krb5 and our local libkrb5support. Adjust a couple of other patches to apply correctly after them. --- 0000-Refactor-cm-functions-in-sendto_kdc.c.patch | 436 +++++ 0001-Simplify-sendto_kdc.c.patch | 421 +++++ ...elper-to-determine-if-a-KDC-is-the-master.patch | 233 +++ ...5_transport-_strategy-enums-for-k5_sendto.patch | 913 +++++++++++ ...pport-for-TLS-used-by-HTTPS-proxy-support.patch | 187 +++ ...ASN.1-codec-for-KKDCP-s-KDC-PROXY-MESSAGE.patch | 352 ++++ ...ch-style-protocol-switching-for-transport.patch | 506 ++++++ ...transport-Microsoft-KKDCPP-implementation.patch | 858 ++++++++++ 0008-Load-custom-anchors-when-using-KKDCP.patch | 312 ++++ ...mes-in-the-server-s-cert-when-using-KKDCP.patch | 562 +++++++ 0010-Add-some-longer-form-docs-for-HTTPS.patch | 86 + ...-k5test.py-provide-runenv-to-python-tests.patch | 64 + 0012-Add-a-simple-KDC-proxy-test-server.patch | 526 ++++++ 0013-Add-tests-for-MS-KKDCP-client-support.patch | 259 +++ krb5-1.10-kpasswd_tcp.patch | 32 - krb5-1.12-system-exts.patch | 2 +- krb5-1.12ish-kpasswd_tcp.patch | 32 + krb5-1.12ish-tls-plugins.patch | 1680 ++++++++++++++++++++ krb5-master-move-otp-sockets.patch | 2 +- krb5.spec | 40 +- 20 files changed, 7468 insertions(+), 35 deletions(-) create mode 100644 0000-Refactor-cm-functions-in-sendto_kdc.c.patch create mode 100644 0001-Simplify-sendto_kdc.c.patch create mode 100644 0002-Add-helper-to-determine-if-a-KDC-is-the-master.patch create mode 100644 0003-Use-k5_transport-_strategy-enums-for-k5_sendto.patch create mode 100644 0004-Build-support-for-TLS-used-by-HTTPS-proxy-support.patch create mode 100644 0005-Add-ASN.1-codec-for-KKDCP-s-KDC-PROXY-MESSAGE.patch create mode 100644 0006-Dispatch-style-protocol-switching-for-transport.patch create mode 100644 0007-HTTPS-transport-Microsoft-KKDCPP-implementation.patch create mode 100644 0008-Load-custom-anchors-when-using-KKDCP.patch create mode 100644 0009-Check-names-in-the-server-s-cert-when-using-KKDCP.patch create mode 100644 0010-Add-some-longer-form-docs-for-HTTPS.patch create mode 100644 0011-Have-k5test.py-provide-runenv-to-python-tests.patch create mode 100644 0012-Add-a-simple-KDC-proxy-test-server.patch create mode 100644 0013-Add-tests-for-MS-KKDCP-client-support.patch delete mode 100644 krb5-1.10-kpasswd_tcp.patch create mode 100644 krb5-1.12ish-kpasswd_tcp.patch create mode 100644 krb5-1.12ish-tls-plugins.patch diff --git a/0000-Refactor-cm-functions-in-sendto_kdc.c.patch b/0000-Refactor-cm-functions-in-sendto_kdc.c.patch new file mode 100644 index 0000000..ff332b1 --- /dev/null +++ b/0000-Refactor-cm-functions-in-sendto_kdc.c.patch @@ -0,0 +1,436 @@ +Tweaked a bit to apply to 1.12: +* krb5_int64 hadn't been replaced by int64_t yet. + +commit 346883c48f1b9e09b1af2cf73e3b96ee8f934072 +Author: Greg Hudson +Date: Wed Mar 26 13:21:45 2014 -0400 + + Refactor cm functions in sendto_kdc.c + + Move get_curtime_ms and the cm functions near the top of the file + right after structure definitions. Except for cm_select_or_poll, + define each cm function separately for poll and for select, since the + implementations don't share much in common. Instead of + cm_unset_write, define cm_read and cm_write functions to put an fd in + read-only or write-only state. Remove the ssflags argument from + cm_add_fd and just expect the caller to make a subsequent call to + cm_read or cm_write. Always select for exceptions when using select. + (Polling for exceptions is implicit with poll). + + With these changes, we no longer select/poll for reading on a TCP + connection until we are done writing to it. So in service_tcp_fd, + remove the check for unexpected read events. + +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index e60a375..e773a0a 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -59,8 +59,7 @@ + + typedef krb5_int64 time_ms; + +-/* Since fd_set is large on some platforms (8K on AIX 5.2), this probably +- * shouldn't be allocated in automatic storage. */ ++/* This can be pretty large, so should not be stack-allocated. */ + struct select_state { + #ifdef USE_POLL + struct pollfd fds[MAX_POLLFDS]; +@@ -107,6 +106,183 @@ struct conn_state { + time_ms endtime; + }; + ++/* Get current time in milliseconds. */ ++static krb5_error_code ++get_curtime_ms(time_ms *time_out) ++{ ++ struct timeval tv; ++ ++ if (gettimeofday(&tv, 0)) ++ return errno; ++ *time_out = (time_ms)tv.tv_sec * 1000 + tv.tv_usec / 1000; ++ return 0; ++} ++ ++#ifdef USE_POLL ++ ++/* Find a pollfd in selstate by fd, or abort if we can't find it. */ ++static inline struct pollfd * ++find_pollfd(struct select_state *selstate, int fd) ++{ ++ int i; ++ ++ for (i = 0; i < selstate->nfds; i++) { ++ if (selstate->fds[i].fd == fd) ++ return &selstate->fds[i]; ++ } ++ abort(); ++} ++ ++static void ++cm_init_selstate(struct select_state *selstate) ++{ ++ selstate->nfds = 0; ++} ++ ++static krb5_boolean ++cm_add_fd(struct select_state *selstate, int fd) ++{ ++ if (selstate->nfds >= MAX_POLLFDS) ++ return FALSE; ++ selstate->fds[selstate->nfds].fd = fd; ++ selstate->fds[selstate->nfds].events = 0; ++ selstate->nfds++; ++ return TRUE; ++} ++ ++static void ++cm_remove_fd(struct select_state *selstate, int fd) ++{ ++ struct pollfd *pfd = find_pollfd(selstate, fd); ++ ++ *pfd = selstate->fds[selstate->nfds - 1]; ++ selstate->nfds--; ++} ++ ++/* Poll for reading (and not writing) on fd the next time we poll. */ ++static void ++cm_read(struct select_state *selstate, int fd) ++{ ++ find_pollfd(selstate, fd)->events = POLLIN; ++} ++ ++/* Poll for writing (and not reading) on fd the next time we poll. */ ++static void ++cm_write(struct select_state *selstate, int fd) ++{ ++ find_pollfd(selstate, fd)->events = POLLOUT; ++} ++ ++/* Get the output events for fd in the form of ssflags. */ ++static unsigned int ++cm_get_ssflags(struct select_state *selstate, int fd) ++{ ++ struct pollfd *pfd = find_pollfd(selstate, fd); ++ ++ return ((pfd->revents & POLLIN) ? SSF_READ : 0) | ++ ((pfd->revents & POLLOUT) ? SSF_WRITE : 0) | ++ ((pfd->revents & POLLERR) ? SSF_EXCEPTION : 0); ++} ++ ++#else /* not USE_POLL */ ++ ++static void ++cm_init_selstate(struct select_state *selstate) ++{ ++ selstate->nfds = 0; ++ selstate->max = 0; ++ FD_ZERO(&selstate->rfds); ++ FD_ZERO(&selstate->wfds); ++ FD_ZERO(&selstate->xfds); ++} ++ ++static krb5_boolean ++cm_add_fd(struct select_state *selstate, int fd) ++{ ++#ifndef _WIN32 /* On Windows FD_SETSIZE is a count, not a max value. */ ++ if (fd >= FD_SETSIZE) ++ return FALSE; ++#endif ++ FD_SET(fd, &selstate->xfds); ++ if (selstate->max <= fd) ++ selstate->max = fd + 1; ++ selstate->nfds++; ++ return TRUE; ++} ++ ++static void ++cm_remove_fd(struct select_state *selstate, int fd) ++{ ++ FD_CLR(fd, &selstate->rfds); ++ FD_CLR(fd, &selstate->wfds); ++ FD_CLR(fd, &selstate->xfds); ++ if (selstate->max == fd + 1) { ++ while (selstate->max > 0 && ++ !FD_ISSET(selstate->max - 1, &selstate->rfds) && ++ !FD_ISSET(selstate->max - 1, &selstate->wfds) && ++ !FD_ISSET(selstate->max - 1, &selstate->xfds)) ++ selstate->max--; ++ } ++ selstate->nfds--; ++} ++ ++/* Select for reading (and not writing) on fd the next time we select. */ ++static void ++cm_read(struct select_state *selstate, int fd) ++{ ++ FD_SET(fd, &selstate->rfds); ++ FD_CLR(fd, &selstate->wfds); ++} ++ ++/* Select for writing (and not reading) on fd the next time we select. */ ++static void ++cm_write(struct select_state *selstate, int fd) ++{ ++ FD_CLR(fd, &selstate->rfds); ++ FD_SET(fd, &selstate->wfds); ++} ++ ++/* Get the events for fd from selstate after a select. */ ++static unsigned int ++cm_get_ssflags(struct select_state *selstate, int fd) ++{ ++ return (FD_ISSET(fd, &selstate->rfds) ? SSF_READ : 0) | ++ (FD_ISSET(fd, &selstate->wfds) ? SSF_WRITE : 0) | ++ (FD_ISSET(fd, &selstate->xfds) ? SSF_EXCEPTION : 0); ++} ++ ++#endif /* not USE_POLL */ ++ ++static krb5_error_code ++cm_select_or_poll(const struct select_state *in, time_ms endtime, ++ struct select_state *out, int *sret) ++{ ++#ifndef USE_POLL ++ struct timeval tv; ++#endif ++ krb5_error_code retval; ++ time_ms curtime, interval; ++ ++ retval = get_curtime_ms(&curtime); ++ if (retval != 0) ++ return retval; ++ interval = (curtime < endtime) ? endtime - curtime : 0; ++ ++ /* We don't need a separate copy of the selstate for poll, but use one for ++ * consistency with how we use select. */ ++ *out = *in; ++ ++#ifdef USE_POLL ++ *sret = poll(out->fds, out->nfds, interval); ++#else ++ tv.tv_sec = interval / 1000; ++ tv.tv_usec = interval % 1000 * 1000; ++ *sret = select(out->max, &out->rfds, &out->wfds, &out->xfds, &tv); ++#endif ++ ++ return (*sret < 0) ? SOCKET_ERRNO : 0; ++} ++ + static int + in_addrlist(struct server_entry *entry, struct serverlist *list) + { +@@ -251,18 +427,6 @@ cleanup: + return retval; + } + +-/* Get current time in milliseconds. */ +-static krb5_error_code +-get_curtime_ms(time_ms *time_out) +-{ +- struct timeval tv; +- +- if (gettimeofday(&tv, 0)) +- return errno; +- *time_out = (time_ms)tv.tv_sec * 1000 + tv.tv_usec / 1000; +- return 0; +-} +- + /* + * Notes: + * +@@ -283,144 +447,6 @@ get_curtime_ms(time_ms *time_out) + * connections already in progress + */ + +-static void +-cm_init_selstate(struct select_state *selstate) +-{ +- selstate->nfds = 0; +-#ifndef USE_POLL +- selstate->max = 0; +- FD_ZERO(&selstate->rfds); +- FD_ZERO(&selstate->wfds); +- FD_ZERO(&selstate->xfds); +-#endif +-} +- +-static krb5_boolean +-cm_add_fd(struct select_state *selstate, int fd, unsigned int ssflags) +-{ +-#ifdef USE_POLL +- if (selstate->nfds >= MAX_POLLFDS) +- return FALSE; +- selstate->fds[selstate->nfds].fd = fd; +- selstate->fds[selstate->nfds].events = 0; +- if (ssflags & SSF_READ) +- selstate->fds[selstate->nfds].events |= POLLIN; +- if (ssflags & SSF_WRITE) +- selstate->fds[selstate->nfds].events |= POLLOUT; +-#else +-#ifndef _WIN32 /* On Windows FD_SETSIZE is a count, not a max value. */ +- if (fd >= FD_SETSIZE) +- return FALSE; +-#endif +- if (ssflags & SSF_READ) +- FD_SET(fd, &selstate->rfds); +- if (ssflags & SSF_WRITE) +- FD_SET(fd, &selstate->wfds); +- if (ssflags & SSF_EXCEPTION) +- FD_SET(fd, &selstate->xfds); +- if (selstate->max <= fd) +- selstate->max = fd + 1; +-#endif +- selstate->nfds++; +- return TRUE; +-} +- +-static void +-cm_remove_fd(struct select_state *selstate, int fd) +-{ +-#ifdef USE_POLL +- int i; +- +- /* Find the FD in the array and move the last entry to its place. */ +- assert(selstate->nfds > 0); +- for (i = 0; i < selstate->nfds && selstate->fds[i].fd != fd; i++); +- assert(i < selstate->nfds); +- selstate->fds[i] = selstate->fds[selstate->nfds - 1]; +-#else +- FD_CLR(fd, &selstate->rfds); +- FD_CLR(fd, &selstate->wfds); +- FD_CLR(fd, &selstate->xfds); +- if (selstate->max == 1 + fd) { +- while (selstate->max > 0 +- && ! FD_ISSET(selstate->max-1, &selstate->rfds) +- && ! FD_ISSET(selstate->max-1, &selstate->wfds) +- && ! FD_ISSET(selstate->max-1, &selstate->xfds)) +- selstate->max--; +- } +-#endif +- selstate->nfds--; +-} +- +-static void +-cm_unset_write(struct select_state *selstate, int fd) +-{ +-#ifdef USE_POLL +- int i; +- +- for (i = 0; i < selstate->nfds && selstate->fds[i].fd != fd; i++); +- assert(i < selstate->nfds); +- selstate->fds[i].events &= ~POLLOUT; +-#else +- FD_CLR(fd, &selstate->wfds); +-#endif +-} +- +-static krb5_error_code +-cm_select_or_poll(const struct select_state *in, time_ms endtime, +- struct select_state *out, int *sret) +-{ +-#ifndef USE_POLL +- struct timeval tv; +-#endif +- krb5_error_code retval; +- time_ms curtime, interval; +- +- retval = get_curtime_ms(&curtime); +- if (retval != 0) +- return retval; +- interval = (curtime < endtime) ? endtime - curtime : 0; +- +- /* We don't need a separate copy of the selstate for poll, but use one for +- * consistency with how we use select. */ +- *out = *in; +- +-#ifdef USE_POLL +- *sret = poll(out->fds, out->nfds, interval); +-#else +- tv.tv_sec = interval / 1000; +- tv.tv_usec = interval % 1000 * 1000; +- *sret = select(out->max, &out->rfds, &out->wfds, &out->xfds, &tv); +-#endif +- +- return (*sret < 0) ? SOCKET_ERRNO : 0; +-} +- +-static unsigned int +-cm_get_ssflags(struct select_state *selstate, int fd) +-{ +- unsigned int ssflags = 0; +-#ifdef USE_POLL +- int i; +- +- for (i = 0; i < selstate->nfds && selstate->fds[i].fd != fd; i++); +- assert(i < selstate->nfds); +- if (selstate->fds[i].revents & POLLIN) +- ssflags |= SSF_READ; +- if (selstate->fds[i].revents & POLLOUT) +- ssflags |= SSF_WRITE; +- if (selstate->fds[i].revents & POLLERR) +- ssflags |= SSF_EXCEPTION; +-#else +- if (FD_ISSET(fd, &selstate->rfds)) +- ssflags |= SSF_READ; +- if (FD_ISSET(fd, &selstate->wfds)) +- ssflags |= SSF_WRITE; +- if (FD_ISSET(fd, &selstate->xfds)) +- ssflags |= SSF_EXCEPTION; +-#endif +- return ssflags; +-} +- + static int service_tcp_fd(krb5_context context, struct conn_state *conn, + struct select_state *selstate, int ssflags); + static int service_udp_fd(krb5_context context, struct conn_state *conn, +@@ -600,7 +626,6 @@ start_connection(krb5_context context, struct conn_state *state, + struct sendto_callback_info *callback_info) + { + int fd, e; +- unsigned int ssflags; + static const int one = 1; + static const struct linger lopt = { 0, 0 }; + +@@ -676,15 +701,17 @@ start_connection(krb5_context context, struct conn_state *state, + state->state = READING; + } + } +- ssflags = SSF_READ | SSF_EXCEPTION; +- if (state->state == CONNECTING || state->state == WRITING) +- ssflags |= SSF_WRITE; +- if (!cm_add_fd(selstate, state->fd, ssflags)) { ++ ++ if (!cm_add_fd(selstate, state->fd)) { + (void) closesocket(state->fd); + state->fd = INVALID_SOCKET; + state->state = FAILED; + return -1; + } ++ if (state->state == CONNECTING || state->state == WRITING) ++ cm_write(selstate, state->fd); ++ else ++ cm_read(selstate, state->fd); + + return 0; + } +@@ -768,9 +795,8 @@ service_tcp_fd(krb5_context context, struct conn_state *conn, + ssize_t nwritten, nread; + SOCKET_WRITEV_TEMP tmp; + +- /* Check for a socket exception or readable data before we expect it. */ +- if (ssflags & SSF_EXCEPTION || +- ((ssflags & SSF_READ) && conn->state != READING)) ++ /* Check for a socket exception. */ ++ if (ssflags & SSF_EXCEPTION) + goto kill_conn; + + switch (conn->state) { +@@ -810,7 +836,7 @@ service_tcp_fd(krb5_context context, struct conn_state *conn, + } + if (conn->x.out.sg_count == 0) { + /* Done writing, switch to reading. */ +- cm_unset_write(selstate, conn->fd); ++ cm_read(selstate, conn->fd); + conn->state = READING; + conn->x.in.bufsizebytes_read = 0; + conn->x.in.bufsize = 0; diff --git a/0001-Simplify-sendto_kdc.c.patch b/0001-Simplify-sendto_kdc.c.patch new file mode 100644 index 0000000..51c512d --- /dev/null +++ b/0001-Simplify-sendto_kdc.c.patch @@ -0,0 +1,421 @@ +From 42b3c2ed11c1e62c1691f868a6796983f93c3beb Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Wed, 9 Apr 2014 13:19:03 -0400 +Subject: [PATCH 01/13] Simplify sendto_kdc.c + +* Get rid of the "x" member of conn_state, which used to be a union + but hasn't been since r14742. +* Define a structure type for the "out" member of conn_state. +* Rename incoming_krb5_message to incoming_message for brevity. +* Make the "pos" member of incoming_message an offset instead of a + pointer, simplifying several present and future computations. +* Use "in" and "out" aliases to the conn_state in and out members + where it improves brevity. +* Rename set_conn_state_msg_length to set_transport_message and give + it a descriptive comment. +* Call set_transport_message from start_connection only, instead of + once in add_connection and perhaps again in start_connection. To + make this possible, pass the original message argument to maybe_send + and start_connection. +* Use make_data and empty_data helpers where appropriate. +--- + src/lib/krb5/os/sendto_kdc.c | 159 +++++++++++++++++++++---------------------- + 1 file changed, 79 insertions(+), 80 deletions(-) + +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index 67e2a60..5f781d3 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -76,30 +76,30 @@ static const char *const state_strings[] = { + + /* connection states */ + enum conn_states { INITIALIZING, CONNECTING, WRITING, READING, FAILED }; +-struct incoming_krb5_message { ++struct incoming_message { + size_t bufsizebytes_read; + size_t bufsize; ++ size_t pos; + char *buf; +- char *pos; + unsigned char bufsizebytes[4]; + size_t n_left; + }; + ++struct outgoing_message { ++ sg_buf sgbuf[2]; ++ sg_buf *sgp; ++ int sg_count; ++ unsigned char msg_len_buf[4]; ++}; ++ + struct conn_state { + SOCKET fd; + enum conn_states state; + int (*service)(krb5_context context, struct conn_state *, + struct select_state *, int); + struct remote_address addr; +- struct { +- struct { +- sg_buf sgbuf[2]; +- sg_buf *sgp; +- int sg_count; +- unsigned char msg_len_buf[4]; +- } out; +- struct incoming_krb5_message in; +- } x; ++ struct incoming_message in; ++ struct outgoing_message out; + krb5_data callback_buffer; + size_t server_index; + struct conn_state *next; +@@ -461,30 +461,31 @@ static int service_tcp_fd(krb5_context context, struct conn_state *conn, + static int service_udp_fd(krb5_context context, struct conn_state *conn, + struct select_state *selstate, int ssflags); + ++/* Set up the actual message we will send across the underlying transport to ++ * communicate the payload message, using one or both of state->out.sgbuf. */ + static void +-set_conn_state_msg_length (struct conn_state *state, const krb5_data *message) ++set_transport_message(struct conn_state *state, const krb5_data *message) + { +- if (!message || message->length == 0) ++ struct outgoing_message *out = &state->out; ++ ++ if (message == NULL || message->length == 0) + return; + + if (state->addr.type == SOCK_STREAM) { +- store_32_be(message->length, state->x.out.msg_len_buf); +- SG_SET(&state->x.out.sgbuf[0], state->x.out.msg_len_buf, 4); +- SG_SET(&state->x.out.sgbuf[1], message->data, message->length); +- state->x.out.sg_count = 2; +- ++ store_32_be(message->length, out->msg_len_buf); ++ SG_SET(&out->sgbuf[0], out->msg_len_buf, 4); ++ SG_SET(&out->sgbuf[1], message->data, message->length); ++ out->sg_count = 2; + } else { +- +- SG_SET(&state->x.out.sgbuf[0], message->data, message->length); +- SG_SET(&state->x.out.sgbuf[1], 0, 0); +- state->x.out.sg_count = 1; +- ++ SG_SET(&out->sgbuf[0], message->data, message->length); ++ SG_SET(&out->sgbuf[1], NULL, 0); ++ out->sg_count = 1; + } + } + + static krb5_error_code + add_connection(struct conn_state **conns, struct addrinfo *ai, +- size_t server_index, const krb5_data *message, char **udpbufp) ++ size_t server_index, char **udpbufp) + { + struct conn_state *state, **tailptr; + +@@ -492,28 +493,26 @@ add_connection(struct conn_state **conns, struct addrinfo *ai, + if (state == NULL) + return ENOMEM; + state->state = INITIALIZING; +- state->x.out.sgp = state->x.out.sgbuf; ++ state->out.sgp = state->out.sgbuf; + state->addr.type = ai->ai_socktype; + state->addr.family = ai->ai_family; + state->addr.len = ai->ai_addrlen; + memcpy(&state->addr.saddr, ai->ai_addr, ai->ai_addrlen); + state->fd = INVALID_SOCKET; + state->server_index = server_index; +- SG_SET(&state->x.out.sgbuf[1], 0, 0); ++ SG_SET(&state->out.sgbuf[1], NULL, 0); + if (ai->ai_socktype == SOCK_STREAM) { + state->service = service_tcp_fd; +- set_conn_state_msg_length (state, message); + } else { + state->service = service_udp_fd; +- set_conn_state_msg_length (state, message); + + if (*udpbufp == NULL) { + *udpbufp = malloc(MAX_DGRAM_SIZE); + if (*udpbufp == 0) + return ENOMEM; + } +- state->x.in.buf = *udpbufp; +- state->x.in.bufsize = MAX_DGRAM_SIZE; ++ state->in.buf = *udpbufp; ++ state->in.bufsize = MAX_DGRAM_SIZE; + } + + /* Chain the new state onto the tail of the list. */ +@@ -597,7 +596,7 @@ resolve_server(krb5_context context, const struct serverlist *servers, + ai.ai_family = entry->family; + ai.ai_addrlen = entry->addrlen; + ai.ai_addr = (struct sockaddr *)&entry->addr; +- return add_connection(conns, &ai, ind, message, udpbufp); ++ return add_connection(conns, &ai, ind, udpbufp); + } + + memset(&hint, 0, sizeof(hint)); +@@ -617,12 +616,12 @@ resolve_server(krb5_context context, const struct serverlist *servers, + /* Add each address with the preferred socktype. */ + retval = 0; + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) +- retval = add_connection(conns, a, ind, message, udpbufp); ++ retval = add_connection(conns, a, ind, udpbufp); + if (retval == 0 && entry->socktype == 0 && socktype2 != 0) { + /* Add each address again with the non-preferred socktype. */ + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) { + a->ai_socktype = socktype2; +- retval = add_connection(conns, a, ind, message, udpbufp); ++ retval = add_connection(conns, a, ind, udpbufp); + } + } + freeaddrinfo(addrs); +@@ -631,7 +630,7 @@ resolve_server(krb5_context context, const struct serverlist *servers, + + static int + start_connection(krb5_context context, struct conn_state *state, +- struct select_state *selstate, ++ const krb5_data *message, struct select_state *selstate, + struct sendto_callback_info *callback_info) + { + int fd, e; +@@ -689,13 +688,14 @@ start_connection(krb5_context context, struct conn_state *state, + return -3; + } + +- set_conn_state_msg_length(state, &state->callback_buffer); ++ message = &state->callback_buffer; + } ++ set_transport_message(state, message); + + if (state->addr.type == SOCK_DGRAM) { + /* Send it now. */ + ssize_t ret; +- sg_buf *sg = &state->x.out.sgbuf[0]; ++ sg_buf *sg = &state->out.sgbuf[0]; + + TRACE_SENDTO_KDC_UDP_SEND_INITIAL(context, &state->addr); + ret = send(state->fd, SG_BUF(sg), SG_LEN(sg), 0); +@@ -731,14 +731,16 @@ start_connection(krb5_context context, struct conn_state *state, + next connection. */ + static int + maybe_send(krb5_context context, struct conn_state *conn, +- struct select_state *selstate, ++ const krb5_data *message, struct select_state *selstate, + struct sendto_callback_info *callback_info) + { + sg_buf *sg; + ssize_t ret; + +- if (conn->state == INITIALIZING) +- return start_connection(context, conn, selstate, callback_info); ++ if (conn->state == INITIALIZING) { ++ return start_connection(context, conn, message, selstate, ++ callback_info); ++ } + + /* Did we already shut down this channel? */ + if (conn->state == FAILED) { +@@ -752,7 +754,7 @@ maybe_send(krb5_context context, struct conn_state *conn, + } + + /* UDP - retransmit after a previous attempt timed out. */ +- sg = &conn->x.out.sgbuf[0]; ++ sg = &conn->out.sgbuf[0]; + TRACE_SENDTO_KDC_UDP_SEND_RETRY(context, &conn->addr); + ret = send(conn->fd, SG_BUF(sg), SG_LEN(sg), 0); + if (ret < 0 || (size_t) ret != SG_LEN(sg)) { +@@ -803,6 +805,8 @@ service_tcp_fd(krb5_context context, struct conn_state *conn, + int e = 0; + ssize_t nwritten, nread; + SOCKET_WRITEV_TEMP tmp; ++ struct incoming_message *in = &conn->in; ++ struct outgoing_message *out = &conn->out; + + /* Check for a socket exception. */ + if (ssflags & SSF_EXCEPTION) +@@ -825,68 +829,68 @@ service_tcp_fd(krb5_context context, struct conn_state *conn, + /* Fall through. */ + case WRITING: + TRACE_SENDTO_KDC_TCP_SEND(context, &conn->addr); +- nwritten = SOCKET_WRITEV(conn->fd, conn->x.out.sgp, +- conn->x.out.sg_count, tmp); ++ nwritten = SOCKET_WRITEV(conn->fd, out->sgp, out->sg_count, tmp); + if (nwritten < 0) { + TRACE_SENDTO_KDC_TCP_ERROR_SEND(context, &conn->addr, + SOCKET_ERRNO); + goto kill_conn; + } + while (nwritten) { +- sg_buf *sgp = conn->x.out.sgp; ++ sg_buf *sgp = out->sgp; + if ((size_t) nwritten < SG_LEN(sgp)) { + SG_ADVANCE(sgp, (size_t) nwritten); + nwritten = 0; + } else { + nwritten -= SG_LEN(sgp); +- conn->x.out.sgp++; +- conn->x.out.sg_count--; ++ out->sgp++; ++ out->sg_count--; + } + } +- if (conn->x.out.sg_count == 0) { ++ if (out->sg_count == 0) { + /* Done writing, switch to reading. */ + cm_read(selstate, conn->fd); + conn->state = READING; +- conn->x.in.bufsizebytes_read = 0; +- conn->x.in.bufsize = 0; +- conn->x.in.buf = 0; +- conn->x.in.pos = 0; +- conn->x.in.n_left = 0; ++ in->bufsizebytes_read = 0; ++ in->bufsize = 0; ++ in->pos = 0; ++ in->buf = NULL; ++ in->n_left = 0; + } + return 0; + + case READING: +- if (conn->x.in.bufsizebytes_read == 4) { ++ if (in->bufsizebytes_read == 4) { + /* Reading data. */ +- nread = SOCKET_READ(conn->fd, conn->x.in.pos, conn->x.in.n_left); ++ nread = SOCKET_READ(conn->fd, &in->buf[in->pos], in->n_left); + if (nread <= 0) { + e = nread ? SOCKET_ERRNO : ECONNRESET; + TRACE_SENDTO_KDC_TCP_ERROR_RECV(context, &conn->addr, e); + goto kill_conn; + } +- conn->x.in.n_left -= nread; +- conn->x.in.pos += nread; +- if (conn->x.in.n_left <= 0) ++ in->n_left -= nread; ++ in->pos += nread; ++ if (in->n_left <= 0) + return 1; + } else { + /* Reading length. */ + nread = SOCKET_READ(conn->fd, +- conn->x.in.bufsizebytes + conn->x.in.bufsizebytes_read, +- 4 - conn->x.in.bufsizebytes_read); ++ in->bufsizebytes + in->bufsizebytes_read, ++ 4 - in->bufsizebytes_read); + if (nread <= 0) { + e = nread ? SOCKET_ERRNO : ECONNRESET; + TRACE_SENDTO_KDC_TCP_ERROR_RECV_LEN(context, &conn->addr, e); + goto kill_conn; + } +- conn->x.in.bufsizebytes_read += nread; +- if (conn->x.in.bufsizebytes_read == 4) { +- unsigned long len = load_32_be (conn->x.in.bufsizebytes); ++ in->bufsizebytes_read += nread; ++ if (in->bufsizebytes_read == 4) { ++ unsigned long len = load_32_be(in->bufsizebytes); + /* Arbitrary 1M cap. */ + if (len > 1 * 1024 * 1024) + goto kill_conn; +- conn->x.in.bufsize = conn->x.in.n_left = len; +- conn->x.in.buf = conn->x.in.pos = malloc(len); +- if (conn->x.in.buf == 0) ++ in->bufsize = in->n_left = len; ++ in->pos = 0; ++ in->buf = malloc(len); ++ if (in->buf == NULL) + goto kill_conn; + } + } +@@ -915,13 +919,13 @@ service_udp_fd(krb5_context context, struct conn_state *conn, + if (conn->state != READING) + abort(); + +- nread = recv(conn->fd, conn->x.in.buf, conn->x.in.bufsize, 0); ++ nread = recv(conn->fd, conn->in.buf, conn->in.bufsize, 0); + if (nread < 0) { + TRACE_SENDTO_KDC_UDP_ERROR_RECV(context, &conn->addr, SOCKET_ERRNO); + kill_conn(conn, selstate); + return 0; + } +- conn->x.in.pos = conn->x.in.buf + nread; ++ conn->in.pos = nread; + return 1; + } + +@@ -986,10 +990,7 @@ service_fds(krb5_context context, struct select_state *selstate, + int stop = 1; + + if (msg_handler != NULL) { +- krb5_data reply; +- +- reply.data = state->x.in.buf; +- reply.length = state->x.in.pos - state->x.in.buf; ++ krb5_data reply = make_data(state->in.buf, state->in.pos); + + stop = (msg_handler(context, &reply, msg_handler_data) != 0); + } +@@ -1051,8 +1052,7 @@ k5_sendto(krb5_context context, const krb5_data *message, + char *udpbuf = NULL; + krb5_boolean done = FALSE; + +- reply->data = 0; +- reply->length = 0; ++ *reply = empty_data(); + + /* One for use here, listing all our fds in use, and one for + * temporary use in service_fds, for the fds of interest. */ +@@ -1077,7 +1077,7 @@ k5_sendto(krb5_context context, const krb5_data *message, + /* Contact each new connection whose socktype matches socktype1. */ + if (state->addr.type != socktype1) + continue; +- if (maybe_send(context, state, sel_state, callback_info)) ++ if (maybe_send(context, state, message, sel_state, callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + msg_handler, msg_handler_data, &winner); +@@ -1089,7 +1089,7 @@ k5_sendto(krb5_context context, const krb5_data *message, + for (state = conns; state != NULL && !done; state = state->next) { + if (state->addr.type != socktype2) + continue; +- if (maybe_send(context, state, sel_state, callback_info)) ++ if (maybe_send(context, state, message, sel_state, callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + msg_handler, msg_handler_data, &winner); +@@ -1105,7 +1105,7 @@ k5_sendto(krb5_context context, const krb5_data *message, + delay = 4000; + for (pass = 1; pass < MAX_PASS && !done; pass++) { + for (state = conns; state != NULL && !done; state = state->next) { +- if (maybe_send(context, state, sel_state, callback_info)) ++ if (maybe_send(context, state, message, sel_state, callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + msg_handler, msg_handler_data, &winner); +@@ -1127,10 +1127,9 @@ k5_sendto(krb5_context context, const krb5_data *message, + goto cleanup; + } + /* Success! */ +- reply->data = winner->x.in.buf; +- reply->length = winner->x.in.pos - winner->x.in.buf; ++ *reply = make_data(winner->in.buf, winner->in.pos); + retval = 0; +- winner->x.in.buf = NULL; ++ winner->in.buf = NULL; + if (server_used != NULL) + *server_used = winner->server_index; + if (remoteaddr != NULL && remoteaddrlen != 0 && *remoteaddrlen > 0) +@@ -1142,8 +1141,8 @@ cleanup: + next = state->next; + if (state->fd != INVALID_SOCKET) + closesocket(state->fd); +- if (state->state == READING && state->x.in.buf != udpbuf) +- free(state->x.in.buf); ++ if (state->state == READING && state->in.buf != udpbuf) ++ free(state->in.buf); + if (callback_info) { + callback_info->pfn_cleanup(callback_info->data, + &state->callback_buffer); +-- +2.1.0 + diff --git a/0002-Add-helper-to-determine-if-a-KDC-is-the-master.patch b/0002-Add-helper-to-determine-if-a-KDC-is-the-master.patch new file mode 100644 index 0000000..50ae55b --- /dev/null +++ b/0002-Add-helper-to-determine-if-a-KDC-is-the-master.patch @@ -0,0 +1,233 @@ +From f4b1a7e7b80ce68e57912edcd48c39ea62c73e43 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Sun, 6 Apr 2014 18:06:14 -0400 +Subject: [PATCH 02/13] Add helper to determine if a KDC is the master + +Add a new function k5_kdc_is_master in locate_kdc.c to determine +whether a KDC matches one of the masters, and use it in +krb5_sendto_kdc. +--- + src/lib/krb5/os/locate_kdc.c | 110 +++++++++++++++++++++++++++++-------------- + src/lib/krb5/os/os-proto.h | 3 ++ + src/lib/krb5/os/sendto_kdc.c | 31 +----------- + 3 files changed, 80 insertions(+), 64 deletions(-) + +diff --git a/src/lib/krb5/os/locate_kdc.c b/src/lib/krb5/os/locate_kdc.c +index 88d55a8..4479465 100644 +--- a/src/lib/krb5/os/locate_kdc.c ++++ b/src/lib/krb5/os/locate_kdc.c +@@ -166,6 +166,24 @@ add_host_to_list(struct serverlist *list, const char *hostname, int port, + return 0; + } + ++/* Return true if server is identical to an entry in list. */ ++static krb5_boolean ++server_list_contains(struct serverlist *list, struct server_entry *server) ++{ ++ struct server_entry *ent; ++ ++ for (ent = list->servers; ent < list->servers + list->nservers; ent++) { ++ if (server->hostname != NULL && ent->hostname != NULL && ++ strcmp(server->hostname, ent->hostname) == 0) ++ return TRUE; ++ if (server->hostname == NULL && ent->hostname == NULL && ++ server->addrlen == ent->addrlen && ++ memcmp(&server->addr, &ent->addr, server->addrlen) == 0) ++ return TRUE; ++ } ++ return FALSE; ++} ++ + static krb5_error_code + locate_srv_conf_1(krb5_context context, const krb5_data *realm, + const char * name, struct serverlist *serverlist, +@@ -529,6 +547,41 @@ dns_locate_server(krb5_context context, const krb5_data *realm, + } + #endif /* KRB5_DNS_LOOKUP */ + ++static krb5_error_code ++locate_server(krb5_context context, const krb5_data *realm, ++ struct serverlist *serverlist, enum locate_service_type svc, ++ int socktype) ++{ ++ krb5_error_code ret; ++ struct serverlist list = SERVERLIST_INIT; ++ ++ *serverlist = list; ++ ++ /* Try modules. If a module returns 0 but leaves the list empty, return an ++ * empty list. */ ++ ret = module_locate_server(context, realm, &list, svc, socktype); ++ if (ret != KRB5_PLUGIN_NO_HANDLE) ++ goto done; ++ ++ /* Try the profile. Fall back to DNS if it returns an empty list. */ ++ ret = prof_locate_server(context, realm, &list, svc, socktype); ++ if (ret) ++ goto done; ++ ++#ifdef KRB5_DNS_LOOKUP ++ if (list.nservers == 0) ++ ret = dns_locate_server(context, realm, &list, svc, socktype); ++#endif ++ ++done: ++ if (ret) { ++ k5_free_serverlist(&list); ++ return ret; ++ } ++ *serverlist = list; ++ return 0; ++} ++ + /* + * Wrapper function for the various backends + */ +@@ -538,54 +591,26 @@ k5_locate_server(krb5_context context, const krb5_data *realm, + struct serverlist *serverlist, enum locate_service_type svc, + int socktype) + { +- krb5_error_code code; +- struct serverlist al = SERVERLIST_INIT; +- +- *serverlist = al; ++ krb5_error_code ret; + ++ memset(serverlist, 0, sizeof(*serverlist)); + if (realm == NULL || realm->data == NULL || realm->data[0] == 0) { + krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE, + "Cannot find KDC for invalid realm name \"\""); + return KRB5_REALM_CANT_RESOLVE; + } + +- code = module_locate_server(context, realm, &al, svc, socktype); +- Tprintf("module_locate_server returns %d\n", code); +- if (code == KRB5_PLUGIN_NO_HANDLE) { +- /* +- * We always try the local file before DNS. Note that there +- * is no way to indicate "service not available" via the +- * config file. +- */ +- +- code = prof_locate_server(context, realm, &al, svc, socktype); +- +-#ifdef KRB5_DNS_LOOKUP +- if (code == 0 && al.nservers == 0) +- code = dns_locate_server(context, realm, &al, svc, socktype); +-#endif /* KRB5_DNS_LOOKUP */ ++ ret = locate_server(context, realm, serverlist, svc, socktype); ++ if (ret) ++ return ret; + +- /* We could put more heuristics here, like looking up a hostname +- of "kerberos."+REALM, etc. */ +- } +- if (code == 0) +- Tprintf ("krb5int_locate_server found %d addresses\n", +- al.nservers); +- else +- Tprintf ("krb5int_locate_server returning error code %d/%s\n", +- code, error_message(code)); +- if (code != 0) { +- k5_free_serverlist(&al); +- return code; +- } +- if (al.nservers == 0) { /* No good servers */ +- k5_free_serverlist(&al); ++ if (serverlist->nservers == 0) { ++ k5_free_serverlist(serverlist); + krb5_set_error_message(context, KRB5_REALM_UNKNOWN, + _("Cannot find KDC for realm \"%.*s\""), + realm->length, realm->data); + return KRB5_REALM_UNKNOWN; + } +- *serverlist = al; + return 0; + } + +@@ -598,3 +623,18 @@ k5_locate_kdc(krb5_context context, const krb5_data *realm, + stype = get_masters ? locate_service_master_kdc : locate_service_kdc; + return k5_locate_server(context, realm, serverlist, stype, socktype); + } ++ ++krb5_boolean ++k5_kdc_is_master(krb5_context context, const krb5_data *realm, ++ struct server_entry *server) ++{ ++ struct serverlist list; ++ krb5_boolean found; ++ ++ if (locate_server(context, realm, &list, locate_service_master_kdc, ++ server->socktype) != 0) ++ return FALSE; ++ found = server_list_contains(&list, server); ++ k5_free_serverlist(&list); ++ return found; ++} +diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h +index c6b730f..9125ba0 100644 +--- a/src/lib/krb5/os/os-proto.h ++++ b/src/lib/krb5/os/os-proto.h +@@ -76,6 +76,9 @@ krb5_error_code k5_locate_kdc(krb5_context context, const krb5_data *realm, + struct serverlist *serverlist, int get_masters, + int socktype); + ++krb5_boolean k5_kdc_is_master(krb5_context context, const krb5_data *realm, ++ struct server_entry *server); ++ + void k5_free_serverlist(struct serverlist *); + + #ifdef HAVE_NETINET_IN_H +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index 5f781d3..e3855a3 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -293,25 +293,6 @@ cm_select_or_poll(const struct select_state *in, time_ms endtime, + } + + static int +-in_addrlist(struct server_entry *entry, struct serverlist *list) +-{ +- size_t i; +- struct server_entry *le; +- +- for (i = 0; i < list->nservers; i++) { +- le = &list->servers[i]; +- if (entry->hostname != NULL && le->hostname != NULL && +- strcmp(entry->hostname, le->hostname) == 0) +- return 1; +- if (entry->hostname == NULL && le->hostname == NULL && +- entry->addrlen == le->addrlen && +- memcmp(&entry->addr, &le->addr, entry->addrlen) == 0) +- return 1; +- } +- return 0; +-} +- +-static int + check_for_svc_unavailable (krb5_context context, + const krb5_data *reply, + void *msg_handler_data) +@@ -418,17 +399,9 @@ krb5_sendto_kdc(krb5_context context, const krb5_data *message, + /* Set use_master to 1 if we ended up talking to a master when we didn't + * explicitly request to. */ + if (*use_master == 0) { +- struct serverlist mservers; +- struct server_entry *entry = &servers.servers[server_used]; +- retval = k5_locate_kdc(context, realm, &mservers, TRUE, +- entry->socktype); +- if (retval == 0) { +- if (in_addrlist(entry, &mservers)) +- *use_master = 1; +- k5_free_serverlist(&mservers); +- } ++ *use_master = k5_kdc_is_master(context, realm, ++ &servers.servers[server_used]); + TRACE_SENDTO_KDC_MASTER(context, *use_master); +- retval = 0; + } + + cleanup: +-- +2.1.0 + diff --git a/0003-Use-k5_transport-_strategy-enums-for-k5_sendto.patch b/0003-Use-k5_transport-_strategy-enums-for-k5_sendto.patch new file mode 100644 index 0000000..ba3f91c --- /dev/null +++ b/0003-Use-k5_transport-_strategy-enums-for-k5_sendto.patch @@ -0,0 +1,913 @@ +From 9c6be00daca0b80aed94ec9680724f95e6be92e1 Mon Sep 17 00:00:00 2001 +From: "Robbie Harwood (frozencemetery)" +Date: Thu, 15 Aug 2013 15:55:52 -0400 +Subject: [PATCH 03/13] Use k5_transport(_strategy) enums for k5_sendto + +In k5_sendto and k5_locate_server, replace "socktype" parameters with +a new enumerator k5_transport, so that we can add new transports which +are not in the socket type namespace. Control the order in which we +make connections of different types using a new k5_transport_strategy +enumerator, to simplify the logic for adding new transports later. +Control the result of k5_locate_server with a no_udp boolean rather +than a socket type. + +[ghudson@mit.edu: renamed type to k5_transport; k5_locate_server + no_udp change; clarified commit message; fix for Solaris getaddrinfo] +[kaduk@mit.edu: name variables of type k5_transport 'transport'] +[nalin@redhat.com: use transport rather than sock_type in more places, + add and use k5_transport_strategy, update the test program] + +ticket: 7929 +--- + src/lib/krb5/os/changepw.c | 31 +++++----- + src/lib/krb5/os/hostrealm_domain.c | 2 +- + src/lib/krb5/os/locate_kdc.c | 75 ++++++++++++---------- + src/lib/krb5/os/os-proto.h | 27 +++++--- + src/lib/krb5/os/sendto_kdc.c | 123 +++++++++++++++++++++++-------------- + src/lib/krb5/os/t_locate_kdc.c | 24 ++++---- + src/lib/krb5/os/t_std_conf.c | 2 +- + src/lib/krb5/os/t_trace.c | 6 +- + src/lib/krb5/os/t_trace.ref | 4 +- + src/lib/krb5/os/trace.c | 6 +- + 10 files changed, 178 insertions(+), 122 deletions(-) + +diff --git a/src/lib/krb5/os/changepw.c b/src/lib/krb5/os/changepw.c +index 4d8abd9..a1c9885 100644 +--- a/src/lib/krb5/os/changepw.c ++++ b/src/lib/krb5/os/changepw.c +@@ -59,25 +59,25 @@ struct sendto_callback_context { + + static krb5_error_code + locate_kpasswd(krb5_context context, const krb5_data *realm, +- struct serverlist *serverlist, int socktype) ++ struct serverlist *serverlist, krb5_boolean no_udp) + { + krb5_error_code code; + + code = k5_locate_server(context, realm, serverlist, locate_service_kpasswd, +- socktype); ++ no_udp); + + if (code == KRB5_REALM_CANT_RESOLVE || code == KRB5_REALM_UNKNOWN) { + code = k5_locate_server(context, realm, serverlist, +- locate_service_kadmin, SOCK_STREAM); ++ locate_service_kadmin, TRUE); + if (!code) { +- /* Success with admin_server but now we need to change the +- port number to use DEFAULT_KPASSWD_PORT and the socktype. */ ++ /* Success with admin_server but now we need to change the port ++ * number to use DEFAULT_KPASSWD_PORT and the transport. */ + size_t i; + for (i = 0; i < serverlist->nservers; i++) { + struct server_entry *s = &serverlist->servers[i]; + krb5_ui_2 kpasswd_port = htons(DEFAULT_KPASSWD_PORT); +- if (socktype != SOCK_STREAM) +- s->socktype = socktype; ++ if (!no_udp && s->transport == TCP) ++ s->transport = TCP_OR_UDP; + if (s->hostname != NULL) + s->port = kpasswd_port; + else if (s->family == AF_INET) +@@ -214,7 +214,7 @@ change_set_password(krb5_context context, + krb5_data *result_string) + { + krb5_data chpw_rep; +- krb5_boolean use_tcp = 0; ++ krb5_boolean no_udp = FALSE; + GETSOCKNAME_ARG3_TYPE addrlen; + krb5_error_code code = 0; + char *code_string; +@@ -247,9 +247,10 @@ change_set_password(krb5_context context, + callback_ctx.local_seq_num = callback_ctx.auth_context->local_seq_number; + + do { +- int socktype = (use_tcp ? SOCK_STREAM : SOCK_DGRAM); ++ k5_transport_strategy strategy = no_udp ? NO_UDP : UDP_FIRST; ++ + code = locate_kpasswd(callback_ctx.context, &creds->server->realm, &sl, +- socktype); ++ no_udp); + if (code) + break; + +@@ -260,7 +261,7 @@ change_set_password(krb5_context context, + callback_info.pfn_cleanup = kpasswd_sendto_msg_cleanup; + krb5_free_data_contents(callback_ctx.context, &chpw_rep); + +- code = k5_sendto(callback_ctx.context, NULL, &sl, socktype, 0, ++ code = k5_sendto(callback_ctx.context, NULL, &sl, strategy, + &callback_info, &chpw_rep, ss2sa(&remote_addr), + &addrlen, NULL, NULL, NULL); + if (code) { +@@ -277,9 +278,9 @@ change_set_password(krb5_context context, + result_string); + + if (code) { +- if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !use_tcp) { ++ if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !no_udp) { + k5_free_serverlist(&sl); +- use_tcp = 1; ++ no_udp = 1; + continue; + } + +@@ -305,9 +306,9 @@ change_set_password(krb5_context context, + strncpy(result_code_string->data, code_string, result_code_string->length); + } + +- if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !use_tcp) { ++ if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !no_udp) { + k5_free_serverlist(&sl); +- use_tcp = 1; ++ no_udp = 1; + } else { + break; + } +diff --git a/src/lib/krb5/os/hostrealm_domain.c b/src/lib/krb5/os/hostrealm_domain.c +index dc9cc59..2228df0 100644 +--- a/src/lib/krb5/os/hostrealm_domain.c ++++ b/src/lib/krb5/os/hostrealm_domain.c +@@ -85,7 +85,7 @@ domain_fallback_realm(krb5_context context, krb5_hostrealm_moddata data, + suffix = uhost; + while (limit-- >= 0 && (dot = strchr(suffix, '.')) != NULL) { + drealm = string2data((char *)suffix); +- if (k5_locate_kdc(context, &drealm, &slist, FALSE, SOCK_DGRAM) == 0) { ++ if (k5_locate_kdc(context, &drealm, &slist, FALSE, FALSE) == 0) { + k5_free_serverlist(&slist); + ret = k5_make_realmlist(suffix, realms_out); + goto cleanup; +diff --git a/src/lib/krb5/os/locate_kdc.c b/src/lib/krb5/os/locate_kdc.c +index 4479465..4c8aead 100644 +--- a/src/lib/krb5/os/locate_kdc.c ++++ b/src/lib/krb5/os/locate_kdc.c +@@ -129,7 +129,7 @@ new_server_entry(struct serverlist *list) + + /* Add an address entry to list. */ + static int +-add_addr_to_list(struct serverlist *list, int socktype, int family, ++add_addr_to_list(struct serverlist *list, k5_transport transport, int family, + size_t addrlen, struct sockaddr *addr) + { + struct server_entry *entry; +@@ -137,7 +137,7 @@ add_addr_to_list(struct serverlist *list, int socktype, int family, + entry = new_server_entry(list); + if (entry == NULL) + return ENOMEM; +- entry->socktype = socktype; ++ entry->transport = transport; + entry->family = family; + entry->hostname = NULL; + entry->addrlen = addrlen; +@@ -149,14 +149,14 @@ add_addr_to_list(struct serverlist *list, int socktype, int family, + /* Add a hostname entry to list. */ + static int + add_host_to_list(struct serverlist *list, const char *hostname, int port, +- int socktype, int family) ++ k5_transport transport, int family) + { + struct server_entry *entry; + + entry = new_server_entry(list); + if (entry == NULL) + return ENOMEM; +- entry->socktype = socktype; ++ entry->transport = transport; + entry->family = family; + entry->hostname = strdup(hostname); + if (entry->hostname == NULL) +@@ -187,7 +187,7 @@ server_list_contains(struct serverlist *list, struct server_entry *server) + static krb5_error_code + locate_srv_conf_1(krb5_context context, const krb5_data *realm, + const char * name, struct serverlist *serverlist, +- int socktype, int udpport, int sec_udpport) ++ k5_transport transport, int udpport, int sec_udpport) + { + const char *realm_srv_names[4]; + char **hostlist, *host, *port, *cp; +@@ -255,12 +255,12 @@ locate_srv_conf_1(krb5_context context, const krb5_data *realm, + *cp = '\0'; + } + +- code = add_host_to_list(serverlist, host, p1, socktype, AF_UNSPEC); ++ code = add_host_to_list(serverlist, host, p1, transport, AF_UNSPEC); + /* Second port is for IPv4 UDP only, and should possibly go away as + * it was originally a krb4 compatibility measure. */ + if (code == 0 && p2 != 0 && +- (socktype == 0 || socktype == SOCK_DGRAM)) +- code = add_host_to_list(serverlist, host, p2, SOCK_DGRAM, AF_INET); ++ (transport == TCP_OR_UDP || transport == UDP)) ++ code = add_host_to_list(serverlist, host, p2, UDP, AF_INET); + if (code) + goto cleanup; + } +@@ -278,7 +278,8 @@ krb5_locate_srv_conf(krb5_context context, const krb5_data *realm, + { + krb5_error_code ret; + +- ret = locate_srv_conf_1(context, realm, name, al, 0, udpport, sec_udpport); ++ ret = locate_srv_conf_1(context, realm, name, al, TCP_OR_UDP, udpport, ++ sec_udpport); + if (ret) + return ret; + if (al->nservers == 0) /* Couldn't resolve any KDC names */ +@@ -294,7 +295,7 @@ locate_srv_dns_1(const krb5_data *realm, const char *service, + { + struct srv_dns_entry *head = NULL, *entry = NULL; + krb5_error_code code = 0; +- int socktype; ++ k5_transport transport; + + code = krb5int_make_srv_query_realm(realm, service, protocol, &head); + if (code) +@@ -310,9 +311,9 @@ locate_srv_dns_1(const krb5_data *realm, const char *service, + } + + for (entry = head; entry != NULL; entry = entry->next) { +- socktype = (strcmp(protocol, "_tcp") == 0) ? SOCK_STREAM : SOCK_DGRAM; ++ transport = (strcmp(protocol, "_tcp") == 0) ? TCP : UDP; + code = add_host_to_list(serverlist, entry->host, htons(entry->port), +- socktype, AF_UNSPEC); ++ transport, AF_UNSPEC); + if (code) + goto cleanup; + } +@@ -341,6 +342,7 @@ module_callback(void *cbdata, int socktype, struct sockaddr *sa) + { + struct module_callback_data *d = cbdata; + size_t addrlen; ++ k5_transport transport; + + if (socktype != SOCK_STREAM && socktype != SOCK_DGRAM) + return 0; +@@ -350,7 +352,8 @@ module_callback(void *cbdata, int socktype, struct sockaddr *sa) + addrlen = sizeof(struct sockaddr_in6); + else + return 0; +- if (add_addr_to_list(d->list, socktype, sa->sa_family, addrlen, ++ transport = (socktype == SOCK_STREAM) ? TCP : UDP; ++ if (add_addr_to_list(d->list, transport, sa->sa_family, addrlen, + sa) != 0) { + /* Assumes only error is ENOMEM. */ + d->out_of_mem = 1; +@@ -362,14 +365,14 @@ module_callback(void *cbdata, int socktype, struct sockaddr *sa) + static krb5_error_code + module_locate_server(krb5_context ctx, const krb5_data *realm, + struct serverlist *serverlist, +- enum locate_service_type svc, int socktype) ++ enum locate_service_type svc, k5_transport transport) + { + struct krb5plugin_service_locate_result *res = NULL; + krb5_error_code code; + struct krb5plugin_service_locate_ftable *vtbl = NULL; + void **ptrs; + char *realmz; /* NUL-terminated realm */ +- int i; ++ int socktype, i; + struct module_callback_data cbdata = { 0, }; + const char *msg; + +@@ -413,11 +416,11 @@ module_locate_server(krb5_context ctx, const krb5_data *realm, + if (code) + continue; + +- code = vtbl->lookup(blob, svc, realmz, +- (socktype != 0) ? socktype : SOCK_DGRAM, AF_UNSPEC, ++ socktype = (transport == TCP) ? SOCK_STREAM : SOCK_DGRAM; ++ code = vtbl->lookup(blob, svc, realmz, socktype, AF_UNSPEC, + module_callback, &cbdata); + /* Also ask for TCP addresses if we got UDP addresses and want both. */ +- if (code == 0 && socktype == 0) { ++ if (code == 0 && transport == TCP_OR_UDP) { + code = vtbl->lookup(blob, svc, realmz, SOCK_STREAM, AF_UNSPEC, + module_callback, &cbdata); + if (code == KRB5_PLUGIN_NO_HANDLE) +@@ -459,7 +462,7 @@ module_locate_server(krb5_context ctx, const krb5_data *realm, + static krb5_error_code + prof_locate_server(krb5_context context, const krb5_data *realm, + struct serverlist *serverlist, enum locate_service_type svc, +- int socktype) ++ k5_transport transport) + { + const char *profname; + int dflport1, dflport2 = 0; +@@ -495,7 +498,7 @@ prof_locate_server(krb5_context context, const krb5_data *realm, + return EBUSY; /* XXX */ + } + +- return locate_srv_conf_1(context, realm, profname, serverlist, socktype, ++ return locate_srv_conf_1(context, realm, profname, serverlist, transport, + dflport1, dflport2); + } + +@@ -503,7 +506,7 @@ prof_locate_server(krb5_context context, const krb5_data *realm, + static krb5_error_code + dns_locate_server(krb5_context context, const krb5_data *realm, + struct serverlist *serverlist, enum locate_service_type svc, +- int socktype) ++ k5_transport transport) + { + const char *dnsname; + int use_dns = _krb5_use_dns_kdc(context); +@@ -533,12 +536,12 @@ dns_locate_server(krb5_context context, const krb5_data *realm, + } + + code = 0; +- if (socktype == SOCK_DGRAM || socktype == 0) { ++ if (transport == UDP || transport == TCP_OR_UDP) { + code = locate_srv_dns_1(realm, dnsname, "_udp", serverlist); + if (code) + Tprintf("dns udp lookup returned error %d\n", code); + } +- if ((socktype == SOCK_STREAM || socktype == 0) && code == 0) { ++ if ((transport == TCP || transport == TCP_OR_UDP) && code == 0) { + code = locate_srv_dns_1(realm, dnsname, "_tcp", serverlist); + if (code) + Tprintf("dns tcp lookup returned error %d\n", code); +@@ -547,10 +550,16 @@ dns_locate_server(krb5_context context, const krb5_data *realm, + } + #endif /* KRB5_DNS_LOOKUP */ + ++/* ++ * Try all of the server location methods in sequence. transport must be ++ * TCP_OR_UDP, TCP, or UDP. It is applied to hostname entries in the profile ++ * and affects whether we query modules or DNS for UDP or TCP or both, but does ++ * not restrict a method from returning entries of other transports. ++ */ + static krb5_error_code + locate_server(krb5_context context, const krb5_data *realm, + struct serverlist *serverlist, enum locate_service_type svc, +- int socktype) ++ k5_transport transport) + { + krb5_error_code ret; + struct serverlist list = SERVERLIST_INIT; +@@ -559,18 +568,18 @@ locate_server(krb5_context context, const krb5_data *realm, + + /* Try modules. If a module returns 0 but leaves the list empty, return an + * empty list. */ +- ret = module_locate_server(context, realm, &list, svc, socktype); ++ ret = module_locate_server(context, realm, &list, svc, transport); + if (ret != KRB5_PLUGIN_NO_HANDLE) + goto done; + + /* Try the profile. Fall back to DNS if it returns an empty list. */ +- ret = prof_locate_server(context, realm, &list, svc, socktype); ++ ret = prof_locate_server(context, realm, &list, svc, transport); + if (ret) + goto done; + + #ifdef KRB5_DNS_LOOKUP + if (list.nservers == 0) +- ret = dns_locate_server(context, realm, &list, svc, socktype); ++ ret = dns_locate_server(context, realm, &list, svc, transport); + #endif + + done: +@@ -589,9 +598,10 @@ done: + krb5_error_code + k5_locate_server(krb5_context context, const krb5_data *realm, + struct serverlist *serverlist, enum locate_service_type svc, +- int socktype) ++ krb5_boolean no_udp) + { + krb5_error_code ret; ++ k5_transport transport = no_udp ? TCP : TCP_OR_UDP; + + memset(serverlist, 0, sizeof(*serverlist)); + if (realm == NULL || realm->data == NULL || realm->data[0] == 0) { +@@ -600,7 +610,7 @@ k5_locate_server(krb5_context context, const krb5_data *realm, + return KRB5_REALM_CANT_RESOLVE; + } + +- ret = locate_server(context, realm, serverlist, svc, socktype); ++ ret = locate_server(context, realm, serverlist, svc, transport); + if (ret) + return ret; + +@@ -616,12 +626,13 @@ k5_locate_server(krb5_context context, const krb5_data *realm, + + krb5_error_code + k5_locate_kdc(krb5_context context, const krb5_data *realm, +- struct serverlist *serverlist, int get_masters, int socktype) ++ struct serverlist *serverlist, krb5_boolean get_masters, ++ krb5_boolean no_udp) + { + enum locate_service_type stype; + + stype = get_masters ? locate_service_master_kdc : locate_service_kdc; +- return k5_locate_server(context, realm, serverlist, stype, socktype); ++ return k5_locate_server(context, realm, serverlist, stype, no_udp); + } + + krb5_boolean +@@ -632,7 +643,7 @@ k5_kdc_is_master(krb5_context context, const krb5_data *realm, + krb5_boolean found; + + if (locate_server(context, realm, &list, locate_service_master_kdc, +- server->socktype) != 0) ++ server->transport) != 0) + return FALSE; + found = server_list_contains(&list, server); + k5_free_serverlist(&list); +diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h +index 9125ba0..3196bca 100644 +--- a/src/lib/krb5/os/os-proto.h ++++ b/src/lib/krb5/os/os-proto.h +@@ -38,11 +38,23 @@ + + #include + ++typedef enum { ++ TCP_OR_UDP = 0, ++ TCP, ++ UDP, ++} k5_transport; ++ ++typedef enum { ++ UDP_FIRST = 0, ++ UDP_LAST, ++ NO_UDP, ++} k5_transport_strategy; ++ + /* A single server hostname or address. */ + struct server_entry { + char *hostname; /* NULL -> use addrlen/addr instead */ + int port; /* Used only if hostname set */ +- int socktype; /* May be 0 for UDP/TCP if hostname set */ ++ k5_transport transport; /* May be 0 for UDP/TCP if hostname set */ + int family; /* May be 0 (aka AF_UNSPEC) if hostname set */ + size_t addrlen; + struct sockaddr_storage addr; +@@ -56,8 +68,8 @@ struct serverlist { + #define SERVERLIST_INIT { NULL, 0 } + + struct remote_address { ++ k5_transport transport; + int family; +- int type; + socklen_t len; + struct sockaddr_storage saddr; + }; +@@ -69,12 +81,13 @@ struct sendto_callback_info { + }; + + krb5_error_code k5_locate_server(krb5_context, const krb5_data *realm, +- struct serverlist *, +- enum locate_service_type svc, int socktype); ++ struct serverlist *serverlist, ++ enum locate_service_type svc, ++ krb5_boolean no_udp); + + krb5_error_code k5_locate_kdc(krb5_context context, const krb5_data *realm, +- struct serverlist *serverlist, int get_masters, +- int socktype); ++ struct serverlist *serverlist, ++ krb5_boolean get_masters, krb5_boolean no_udp); + + krb5_boolean k5_kdc_is_master(krb5_context context, const krb5_data *realm, + struct server_entry *server); +@@ -103,7 +116,7 @@ int _krb5_conf_boolean (const char *); + + krb5_error_code k5_sendto(krb5_context context, const krb5_data *message, + const struct serverlist *addrs, +- int socktype1, int socktype2, ++ k5_transport_strategy strategy, + struct sendto_callback_info *callback_info, + krb5_data *reply, struct sockaddr *remoteaddr, + socklen_t *remoteaddrlen, int *server_used, +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index e3855a3..3f99ce8 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -104,6 +104,7 @@ struct conn_state { + size_t server_index; + struct conn_state *next; + time_ms endtime; ++ krb5_boolean defer; + }; + + /* Get current time in milliseconds. */ +@@ -293,6 +294,19 @@ cm_select_or_poll(const struct select_state *in, time_ms endtime, + } + + static int ++socktype_for_transport(k5_transport transport) ++{ ++ switch (transport) { ++ case UDP: ++ return SOCK_DGRAM; ++ case TCP: ++ return SOCK_STREAM; ++ default: ++ return 0; ++ } ++} ++ ++static int + check_for_svc_unavailable (krb5_context context, + const krb5_data *reply, + void *msg_handler_data) +@@ -330,11 +344,12 @@ check_for_svc_unavailable (krb5_context context, + krb5_error_code + krb5_sendto_kdc(krb5_context context, const krb5_data *message, + const krb5_data *realm, krb5_data *reply, int *use_master, +- int tcp_only) ++ int no_udp) + { + krb5_error_code retval, err; + struct serverlist servers; +- int socktype1 = 0, socktype2 = 0, server_used; ++ int server_used; ++ k5_transport_strategy strategy; + + /* + * find KDC location(s) for realm +@@ -349,9 +364,9 @@ krb5_sendto_kdc(krb5_context context, const krb5_data *message, + * should probably be returned as well. + */ + +- TRACE_SENDTO_KDC(context, message->length, realm, *use_master, tcp_only); ++ TRACE_SENDTO_KDC(context, message->length, realm, *use_master, no_udp); + +- if (!tcp_only && context->udp_pref_limit < 0) { ++ if (!no_udp && context->udp_pref_limit < 0) { + int tmp; + retval = profile_get_integer(context->profile, + KRB5_CONF_LIBDEFAULTS, KRB5_CONF_UDP_PREFERENCE_LIMIT, 0, +@@ -368,22 +383,21 @@ krb5_sendto_kdc(krb5_context context, const krb5_data *message, + context->udp_pref_limit = tmp; + } + +- if (tcp_only) +- socktype1 = SOCK_STREAM, socktype2 = 0; ++ if (no_udp) ++ strategy = NO_UDP; + else if (message->length <= (unsigned int) context->udp_pref_limit) +- socktype1 = SOCK_DGRAM, socktype2 = SOCK_STREAM; ++ strategy = UDP_FIRST; + else +- socktype1 = SOCK_STREAM, socktype2 = SOCK_DGRAM; ++ strategy = UDP_LAST; + +- retval = k5_locate_kdc(context, realm, &servers, *use_master, +- tcp_only ? SOCK_STREAM : 0); ++ retval = k5_locate_kdc(context, realm, &servers, *use_master, no_udp); + if (retval) + return retval; + + err = 0; +- retval = k5_sendto(context, message, &servers, socktype1, socktype2, +- NULL, reply, NULL, NULL, &server_used, +- check_for_svc_unavailable, &err); ++ retval = k5_sendto(context, message, &servers, strategy, NULL, reply, ++ NULL, NULL, &server_used, check_for_svc_unavailable, ++ &err); + if (retval == KRB5_KDC_UNREACH) { + if (err == KDC_ERR_SVC_UNAVAILABLE) { + retval = KRB5KDC_ERR_SVC_UNAVAILABLE; +@@ -444,7 +458,7 @@ set_transport_message(struct conn_state *state, const krb5_data *message) + if (message == NULL || message->length == 0) + return; + +- if (state->addr.type == SOCK_STREAM) { ++ if (state->addr.transport == TCP) { + store_32_be(message->length, out->msg_len_buf); + SG_SET(&out->sgbuf[0], out->msg_len_buf, 4); + SG_SET(&out->sgbuf[1], message->data, message->length); +@@ -457,8 +471,9 @@ set_transport_message(struct conn_state *state, const krb5_data *message) + } + + static krb5_error_code +-add_connection(struct conn_state **conns, struct addrinfo *ai, +- size_t server_index, char **udpbufp) ++add_connection(struct conn_state **conns, k5_transport transport, ++ krb5_boolean defer, struct addrinfo *ai, size_t server_index, ++ char **udpbufp) + { + struct conn_state *state, **tailptr; + +@@ -467,14 +482,15 @@ add_connection(struct conn_state **conns, struct addrinfo *ai, + return ENOMEM; + state->state = INITIALIZING; + state->out.sgp = state->out.sgbuf; +- state->addr.type = ai->ai_socktype; ++ state->addr.transport = transport; + state->addr.family = ai->ai_family; + state->addr.len = ai->ai_addrlen; + memcpy(&state->addr.saddr, ai->ai_addr, ai->ai_addrlen); ++ state->defer = defer; + state->fd = INVALID_SOCKET; + state->server_index = server_index; + SG_SET(&state->out.sgbuf[1], NULL, 0); +- if (ai->ai_socktype == SOCK_STREAM) { ++ if (transport == TCP) { + state->service = service_tcp_fd; + } else { + state->service = service_udp_fd; +@@ -549,32 +565,41 @@ translate_ai_error (int err) + */ + static krb5_error_code + resolve_server(krb5_context context, const struct serverlist *servers, +- size_t ind, int socktype1, int socktype2, ++ size_t ind, k5_transport_strategy strategy, + const krb5_data *message, char **udpbufp, + struct conn_state **conns) + { + krb5_error_code retval; + struct server_entry *entry = &servers->servers[ind]; ++ k5_transport transport; + struct addrinfo *addrs, *a, hint, ai; ++ krb5_boolean defer; + int err, result; + char portbuf[64]; + +- /* Skip any stray entries of socktypes we don't want. */ +- if (entry->socktype != 0 && entry->socktype != socktype1 && +- entry->socktype != socktype2) ++ /* Skip UDP entries if we don't want UDP. */ ++ if (strategy == NO_UDP && entry->transport == UDP) + return 0; + ++ transport = (strategy == UDP_FIRST) ? UDP : TCP; + if (entry->hostname == NULL) { +- ai.ai_socktype = entry->socktype; ++ /* Added by a module, so transport is either TCP or UDP. */ ++ ai.ai_socktype = socktype_for_transport(entry->transport); + ai.ai_family = entry->family; + ai.ai_addrlen = entry->addrlen; + ai.ai_addr = (struct sockaddr *)&entry->addr; +- return add_connection(conns, &ai, ind, udpbufp); ++ defer = (entry->transport != transport); ++ return add_connection(conns, entry->transport, defer, &ai, ind, ++ udpbufp); + } + ++ /* If the entry has a specified transport, use it. */ ++ if (entry->transport != TCP_OR_UDP) ++ transport = entry->transport; ++ + memset(&hint, 0, sizeof(hint)); + hint.ai_family = entry->family; +- hint.ai_socktype = (entry->socktype != 0) ? entry->socktype : socktype1; ++ hint.ai_socktype = socktype_for_transport(transport); + hint.ai_flags = AI_ADDRCONFIG; + #ifdef AI_NUMERICSERV + hint.ai_flags |= AI_NUMERICSERV; +@@ -586,15 +611,19 @@ resolve_server(krb5_context context, const struct serverlist *servers, + err = getaddrinfo(entry->hostname, portbuf, &hint, &addrs); + if (err) + return translate_ai_error(err); +- /* Add each address with the preferred socktype. */ ++ ++ /* Add each address with the specified or preferred transport. */ + retval = 0; + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) +- retval = add_connection(conns, a, ind, udpbufp); +- if (retval == 0 && entry->socktype == 0 && socktype2 != 0) { +- /* Add each address again with the non-preferred socktype. */ ++ retval = add_connection(conns, transport, FALSE, a, ind, udpbufp); ++ ++ /* For TCP_OR_UDP entries, add each address again with the non-preferred ++ * transport, unless we are avoiding UDP. Flag these as deferred. */ ++ if (retval == 0 && entry->transport == TCP_OR_UDP && strategy != NO_UDP) { ++ transport = (strategy == UDP_FIRST) ? TCP : UDP; + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) { +- a->ai_socktype = socktype2; +- retval = add_connection(conns, a, ind, udpbufp); ++ a->ai_socktype = socktype_for_transport(transport); ++ retval = add_connection(conns, transport, TRUE, a, ind, udpbufp); + } + } + freeaddrinfo(addrs); +@@ -606,17 +635,18 @@ start_connection(krb5_context context, struct conn_state *state, + const krb5_data *message, struct select_state *selstate, + struct sendto_callback_info *callback_info) + { +- int fd, e; ++ int fd, e, type; + static const int one = 1; + static const struct linger lopt = { 0, 0 }; + +- fd = socket(state->addr.family, state->addr.type, 0); ++ type = socktype_for_transport(state->addr.transport); ++ fd = socket(state->addr.family, type, 0); + if (fd == INVALID_SOCKET) + return -1; /* try other hosts */ + set_cloexec_fd(fd); + /* Make it non-blocking. */ + ioctlsocket(fd, FIONBIO, (const void *) &one); +- if (state->addr.type == SOCK_STREAM) { ++ if (state->addr.transport == TCP) { + setsockopt(fd, SOL_SOCKET, SO_LINGER, &lopt, sizeof(lopt)); + TRACE_SENDTO_KDC_TCP_CONNECT(context, &state->addr); + } +@@ -665,7 +695,7 @@ start_connection(krb5_context context, struct conn_state *state, + } + set_transport_message(state, message); + +- if (state->addr.type == SOCK_DGRAM) { ++ if (state->addr.transport == UDP) { + /* Send it now. */ + ssize_t ret; + sg_buf *sg = &state->out.sgbuf[0]; +@@ -720,7 +750,7 @@ maybe_send(krb5_context context, struct conn_state *conn, + return -1; + } + +- if (conn->addr.type == SOCK_STREAM) { ++ if (conn->addr.transport != UDP) { + /* The select callback will handle flushing any data we + haven't written yet, and we only write it once. */ + return -1; +@@ -910,7 +940,7 @@ get_endtime(time_ms endtime, struct conn_state *conns) + struct conn_state *state; + + for (state = conns; state != NULL; state = state->next) { +- if (state->addr.type == SOCK_STREAM && ++ if (state->addr.transport == TCP && + (state->state == READING || state->state == WRITING) && + state->endtime > endtime) + endtime = state->endtime; +@@ -1008,7 +1038,7 @@ service_fds(krb5_context context, struct select_state *selstate, + + krb5_error_code + k5_sendto(krb5_context context, const krb5_data *message, +- const struct serverlist *servers, int socktype1, int socktype2, ++ const struct serverlist *servers, k5_transport_strategy strategy, + struct sendto_callback_info* callback_info, krb5_data *reply, + struct sockaddr *remoteaddr, socklen_t *remoteaddrlen, + int *server_used, +@@ -1038,17 +1068,18 @@ k5_sendto(krb5_context context, const krb5_data *message, + cm_init_selstate(sel_state); + + /* First pass: resolve server hosts, communicate with resulting addresses +- * of the preferred socktype, and wait 1s for an answer from each. */ ++ * of the preferred transport, and wait 1s for an answer from each. */ + for (s = 0; s < servers->nservers && !done; s++) { + /* Find the current tail pointer. */ + for (tailptr = &conns; *tailptr != NULL; tailptr = &(*tailptr)->next); +- retval = resolve_server(context, servers, s, socktype1, socktype2, +- message, &udpbuf, &conns); ++ retval = resolve_server(context, servers, s, strategy, message, ++ &udpbuf, &conns); + if (retval) + goto cleanup; + for (state = *tailptr; state != NULL && !done; state = state->next) { +- /* Contact each new connection whose socktype matches socktype1. */ +- if (state->addr.type != socktype1) ++ /* Contact each new connection, deferring those which use the ++ * non-preferred RFC 4120 transport. */ ++ if (state->defer) + continue; + if (maybe_send(context, state, message, sel_state, callback_info)) + continue; +@@ -1057,10 +1088,10 @@ k5_sendto(krb5_context context, const krb5_data *message, + } + } + +- /* Complete the first pass by contacting servers of the non-preferred +- * socktype (if given), waiting 1s for an answer from each. */ ++ /* Complete the first pass by contacting servers of the non-preferred RFC ++ * 4120 transport (if given), waiting 1s for an answer from each. */ + for (state = conns; state != NULL && !done; state = state->next) { +- if (state->addr.type != socktype2) ++ if (!state->defer) + continue; + if (maybe_send(context, state, message, sel_state, callback_info)) + continue; +diff --git a/src/lib/krb5/os/t_locate_kdc.c b/src/lib/krb5/os/t_locate_kdc.c +index 5453a4c..300aa71 100644 +--- a/src/lib/krb5/os/t_locate_kdc.c ++++ b/src/lib/krb5/os/t_locate_kdc.c +@@ -29,18 +29,18 @@ kfatal (krb5_error_code err) + } + + static const char * +-stypename (int stype) ++ttypename (k5_transport ttype) + { + static char buf[20]; +- switch (stype) { +- case SOCK_STREAM: +- return "stream"; +- case SOCK_DGRAM: +- return "dgram"; +- case SOCK_RAW: +- return "raw"; ++ switch (ttype) { ++ case TCP_OR_UDP: ++ return "tcp or udp"; ++ case TCP: ++ return "tcp"; ++ case UDP: ++ return "udp"; + default: +- snprintf(buf, sizeof(buf), "?%d", stype); ++ snprintf(buf, sizeof(buf), "?%d", ttype); + return buf; + } + } +@@ -58,7 +58,7 @@ print_addrs (void) + + if (entry->hostname != NULL) { + printf("%2d: host %s\t%s\tport %d\n", (int)i, entry->hostname, +- stypename(entry->socktype), ntohs(entry->port)); ++ ttypename(entry->transport), ntohs(entry->port)); + continue; + } + err = getnameinfo((struct sockaddr *)&entry->addr, entry->addrlen, +@@ -69,7 +69,7 @@ print_addrs (void) + gai_strerror(err)); + } else { + printf("%2d: address %s\t%s\tport %s\n", (int)i, hostbuf, +- stypename(entry->socktype), srvbuf); ++ ttypename(entry->transport), srvbuf); + } + } + } +@@ -129,7 +129,7 @@ main (int argc, char *argv[]) + break; + + case LOOKUP_WHATEVER: +- err = k5_locate_kdc(ctx, &realm, &sl, master, 0); ++ err = k5_locate_kdc(ctx, &realm, &sl, master, FALSE); + break; + } + if (err) kfatal (err); +diff --git a/src/lib/krb5/os/t_std_conf.c b/src/lib/krb5/os/t_std_conf.c +index e2ff572..6ee54d5 100644 +--- a/src/lib/krb5/os/t_std_conf.c ++++ b/src/lib/krb5/os/t_std_conf.c +@@ -82,7 +82,7 @@ test_locate_kdc(krb5_context ctx, char *realm) + + rlm.data = realm; + rlm.length = strlen(realm); +- retval = k5_locate_kdc(ctx, &rlm, &servers, get_masters, 0); ++ retval = k5_locate_kdc(ctx, &rlm, &servers, get_masters, FALSE); + if (retval) { + com_err("krb5_locate_kdc", retval, 0); + return; +diff --git a/src/lib/krb5/os/t_trace.c b/src/lib/krb5/os/t_trace.c +index 36044f5..4cb2bd0 100644 +--- a/src/lib/krb5/os/t_trace.c ++++ b/src/lib/krb5/os/t_trace.c +@@ -112,7 +112,7 @@ main (int argc, char *argv[]) + TRACE(ctx, "size_t and const char *, as four-character hex hash: " + "{hashlenstr}", 1, NULL); + +- ra.type = SOCK_STREAM; ++ ra.transport = TCP; + addr_in = (struct sockaddr_in *)&ra.saddr; + addr_in->sin_family = AF_INET; + addr_in->sin_addr.s_addr = INADDR_ANY; +@@ -121,10 +121,10 @@ main (int argc, char *argv[]) + ra.family = AF_INET; + TRACE(ctx, "struct remote_address *, show socket type, address, port: " + "{raddr}", &ra); +- ra.type = SOCK_DGRAM; ++ ra.transport = UDP; + TRACE(ctx, "struct remote_address *, show socket type, address, port: " + "{raddr}", &ra); +- ra.type = 1234; ++ ra.transport = 1234; + addr_in->sin_family = AF_UNSPEC; + ra.family = AF_UNSPEC; + TRACE(ctx, "struct remote_address *, show socket type, address, port: " +diff --git a/src/lib/krb5/os/t_trace.ref b/src/lib/krb5/os/t_trace.ref +index 749d9c9..ca5818a 100644 +--- a/src/lib/krb5/os/t_trace.ref ++++ b/src/lib/krb5/os/t_trace.ref +@@ -10,8 +10,8 @@ size_t and const char *, as four-character hex hash: 7B9A + size_t and const char *, as four-character hex hash: (null) + struct remote_address *, show socket type, address, port: stream 0.0.0.0:88 + struct remote_address *, show socket type, address, port: dgram 0.0.0.0:88 +-struct remote_address *, show socket type, address, port: socktype1234 AF_UNSPEC +-struct remote_address *, show socket type, address, port: socktype1234 af5678 ++struct remote_address *, show socket type, address, port: transport1234 AF_UNSPEC ++struct remote_address *, show socket type, address, port: transport1234 af5678 + krb5_data *, display as counted string: example.data + krb5_data *, display as counted string: (null) + krb5_data *, display as hex bytes: 6578616D706C652E64617461 +diff --git a/src/lib/krb5/os/trace.c b/src/lib/krb5/os/trace.c +index 525742c..8319a86 100644 +--- a/src/lib/krb5/os/trace.c ++++ b/src/lib/krb5/os/trace.c +@@ -197,12 +197,12 @@ trace_format(krb5_context context, const char *fmt, va_list ap) + } + } else if (strcmp(tmpbuf, "raddr") == 0) { + ra = va_arg(ap, struct remote_address *); +- if (ra->type == SOCK_DGRAM) ++ if (ra->transport == UDP) + k5_buf_add(&buf, "dgram"); +- else if (ra->type == SOCK_STREAM) ++ else if (ra->transport == TCP) + k5_buf_add(&buf, "stream"); + else +- k5_buf_add_fmt(&buf, "socktype%d", ra->type); ++ k5_buf_add_fmt(&buf, "transport%d", ra->transport); + + if (getnameinfo((struct sockaddr *)&ra->saddr, ra->len, + addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), +-- +2.1.0 + diff --git a/0004-Build-support-for-TLS-used-by-HTTPS-proxy-support.patch b/0004-Build-support-for-TLS-used-by-HTTPS-proxy-support.patch new file mode 100644 index 0000000..df4707d --- /dev/null +++ b/0004-Build-support-for-TLS-used-by-HTTPS-proxy-support.patch @@ -0,0 +1,187 @@ +From d0be57ac45ea639baa3cff0dd2108c34e834bfa7 Mon Sep 17 00:00:00 2001 +From: "Robbie Harwood (frozencemetery)" +Date: Fri, 16 Aug 2013 12:45:03 -0400 +Subject: [PATCH 04/13] Build support for TLS used by HTTPS proxy support + +Add a --with-proxy-tls-impl option to configure, taking 'openssl', +'auto', or invocation as --without-proxy-tls-impl. Use related CFLAGS +when building lib/krb5/os, and LIBS when linking libkrb5. Call the +OpenSSL library startup functions during library initialization. + +ticket: 7929 +--- + src/Makefile.in | 1 + + src/config/pre.in | 5 +++++ + src/configure.in | 40 ++++++++++++++++++++++++++++++++++++++++ + src/lib/krb5/Makefile.in | 3 ++- + src/lib/krb5/krb5_libinit.c | 2 ++ + src/lib/krb5/os/Makefile.in | 2 +- + src/lib/krb5/os/os-proto.h | 1 + + src/lib/krb5/os/sendto_kdc.c | 14 ++++++++++++++ + 8 files changed, 66 insertions(+), 2 deletions(-) + +diff --git a/src/Makefile.in b/src/Makefile.in +index 1725093..5e2cf4e 100644 +--- a/src/Makefile.in ++++ b/src/Makefile.in +@@ -553,6 +553,7 @@ pyrunenv.vals: Makefile + for i in $(RUN_VARS); do \ + eval echo 'env['\\\'$$i\\\''] = '\\\'\$$$$i\\\'; \ + done > $@ ++ echo "proxy_tls_impl = '$(PROXY_TLS_IMPL)'" >> $@ + + runenv.py: pyrunenv.vals + echo 'env = {}' > $@ +diff --git a/src/config/pre.in b/src/config/pre.in +index fbc5c11..e1d7e4b 100644 +--- a/src/config/pre.in ++++ b/src/config/pre.in +@@ -428,6 +428,11 @@ PKINIT_CRYPTO_IMPL = @PKINIT_CRYPTO_IMPL@ + PKINIT_CRYPTO_IMPL_CFLAGS = @PKINIT_CRYPTO_IMPL_CFLAGS@ + PKINIT_CRYPTO_IMPL_LIBS = @PKINIT_CRYPTO_IMPL_LIBS@ + ++# TLS implementation selection for HTTPS proxy support ++PROXY_TLS_IMPL = @PROXY_TLS_IMPL@ ++PROXY_TLS_IMPL_CFLAGS = @PROXY_TLS_IMPL_CFLAGS@ ++PROXY_TLS_IMPL_LIBS = @PROXY_TLS_IMPL_LIBS@ ++ + # error table rules + # + ### /* these are invoked as $(...) foo.et, which works, but could be better */ +diff --git a/src/configure.in b/src/configure.in +index 9bc4663..39e3738 100644 +--- a/src/configure.in ++++ b/src/configure.in +@@ -272,6 +272,46 @@ AC_SUBST(PKINIT_CRYPTO_IMPL) + AC_SUBST(PKINIT_CRYPTO_IMPL_CFLAGS) + AC_SUBST(PKINIT_CRYPTO_IMPL_LIBS) + ++# WITH_PROXY_TLS_IMPL ++ ++AC_ARG_WITH([proxy-tls-impl], ++AC_HELP_STRING([--with-proxy-tls-impl=IMPL], ++ [use specified TLS implementation for HTTPS @<:@auto@:>@]), ++[PROXY_TLS_IMPL=$withval],[PROXY_TLS_IMPL=auto]) ++case "$PROXY_TLS_IMPL" in ++openssl|auto) ++ AC_CHECK_LIB(ssl,SSL_CTX_new,[have_lib_ssl=true],[have_lib_ssl=false], ++ -lcrypto) ++ AC_MSG_CHECKING([for OpenSSL]) ++ if test x$have_lib_ssl = xtrue ; then ++ AC_DEFINE(PROXY_TLS_IMPL_OPENSSL,1, ++ [Define if HTTPS TLS implementation is OpenSSL]) ++ AC_MSG_RESULT([yes]) ++ PROXY_TLS_IMPL_LIBS="-lssl -lcrypto" ++ PROXY_TLS_IMPL=openssl ++ AC_MSG_NOTICE(HTTPS support will use TLS from '$PROXY_TLS_IMPL') ++ else ++ if test "$PROXY_TLS_IMPL" = openssl ; then ++ AC_MSG_ERROR([OpenSSL not found!]) ++ else ++ AC_MSG_WARN([OpenSSL not found!]) ++ fi ++ PROXY_TLS_IMPL=no ++ AC_MSG_NOTICE(building without HTTPS support) ++ fi ++ ;; ++no) ++ AC_MSG_NOTICE(building without HTTPS support) ++ ;; ++*) ++ AC_MSG_ERROR([Unsupported HTTPS proxy TLS implementation $withval]) ++ ;; ++esac ++ ++AC_SUBST(PROXY_TLS_IMPL) ++AC_SUBST(PROXY_TLS_IMPL_CFLAGS) ++AC_SUBST(PROXY_TLS_IMPL_LIBS) ++ + AC_ARG_ENABLE([aesni], + AC_HELP_STRING([--disable-aesni],[Do not build with AES-NI support]), , + enable_aesni=check) +diff --git a/src/lib/krb5/Makefile.in b/src/lib/krb5/Makefile.in +index d9cddc1..472c008 100644 +--- a/src/lib/krb5/Makefile.in ++++ b/src/lib/krb5/Makefile.in +@@ -56,7 +56,8 @@ RELDIR=krb5 + SHLIB_EXPDEPS = \ + $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ + $(COM_ERR_DEPLIB) $(SUPPORT_DEPLIB) +-SHLIB_EXPLIBS=-lk5crypto -lcom_err $(SUPPORT_LIB) @GEN_LIB@ $(LIBS) ++SHLIB_EXPLIBS=-lk5crypto -lcom_err $(PROXY_TLS_IMPL_LIBS) $(SUPPORT_LIB) \ ++ @GEN_LIB@ $(LIBS) + + all-unix:: all-liblinks + +diff --git a/src/lib/krb5/krb5_libinit.c b/src/lib/krb5/krb5_libinit.c +index f83d25b..f2382d1 100644 +--- a/src/lib/krb5/krb5_libinit.c ++++ b/src/lib/krb5/krb5_libinit.c +@@ -58,6 +58,8 @@ int krb5int_lib_init(void) + if (err) + return err; + ++ k5_sendto_kdc_initialize(); ++ + return 0; + } + +diff --git a/src/lib/krb5/os/Makefile.in b/src/lib/krb5/os/Makefile.in +index 5add9f9..fb4001a 100644 +--- a/src/lib/krb5/os/Makefile.in ++++ b/src/lib/krb5/os/Makefile.in +@@ -2,7 +2,7 @@ mydir=lib$(S)krb5$(S)os + BUILDTOP=$(REL)..$(S)..$(S).. + DEFINES=-DLIBDIR=\"$(KRB5_LIBDIR)\" -DBINDIR=\"$(CLIENT_BINDIR)\" \ + -DSBINDIR=\"$(ADMIN_BINDIR)\" +-LOCALINCLUDES=-I$(top_srcdir)/util/profile ++LOCALINCLUDES= $(PROXY_TLS_IMPL_CFLAGS) -I$(top_srcdir)/util/profile + + ##DOS##BUILDTOP = ..\..\.. + ##DOS##PREFIXDIR=os +diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h +index 3196bca..f23dda5 100644 +--- a/src/lib/krb5/os/os-proto.h ++++ b/src/lib/krb5/os/os-proto.h +@@ -184,5 +184,6 @@ krb5_error_code localauth_k5login_initvt(krb5_context context, int maj_ver, + krb5_plugin_vtable vtable); + krb5_error_code localauth_an2ln_initvt(krb5_context context, int maj_ver, + int min_ver, krb5_plugin_vtable vtable); ++void k5_sendto_kdc_initialize(void); + + #endif /* KRB5_LIBOS_INT_PROTO__ */ +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index 3f99ce8..c6aae8e 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -48,6 +48,10 @@ + #endif + #endif + ++#ifdef PROXY_TLS_IMPL_OPENSSL ++#include ++#endif ++ + #define MAX_PASS 3 + #define DEFAULT_UDP_PREF_LIMIT 1465 + #define HARD_UDP_LIMIT 32700 /* could probably do 64K-epsilon ? */ +@@ -107,6 +111,16 @@ struct conn_state { + krb5_boolean defer; + }; + ++void ++k5_sendto_kdc_initialize(void) ++{ ++#ifdef PROXY_TLS_IMPL_OPENSSL ++ SSL_library_init(); ++ SSL_load_error_strings(); ++ OpenSSL_add_all_algorithms(); ++#endif ++} ++ + /* Get current time in milliseconds. */ + static krb5_error_code + get_curtime_ms(time_ms *time_out) +-- +2.1.0 + diff --git a/0005-Add-ASN.1-codec-for-KKDCP-s-KDC-PROXY-MESSAGE.patch b/0005-Add-ASN.1-codec-for-KKDCP-s-KDC-PROXY-MESSAGE.patch new file mode 100644 index 0000000..f650d69 --- /dev/null +++ b/0005-Add-ASN.1-codec-for-KKDCP-s-KDC-PROXY-MESSAGE.patch @@ -0,0 +1,352 @@ +From bb89afd7c59deea855d2818fe36ef7472b4abf2e Mon Sep 17 00:00:00 2001 +From: Nathaniel McCallum +Date: Mon, 9 Sep 2013 14:23:56 -0400 +Subject: [PATCH 05/13] Add ASN.1 codec for KKDCP's KDC-PROXY-MESSAGE + +Handle encoding and decoding [MS-KKDCP] proxy messages, including +handling of the additional length bytes. Early versions of [MS-KKDCP] +incorrectly omit that the size of the proxied message is prepended to +the proxied message, as it is when we're using plain TCP, before +encoding the proxy-message structure. This is fixed at least as of +version 2.1 of the spec. + +[nalin@redhat.com: add tests] + +ticket: 7929 +--- + src/include/k5-int.h | 13 +++++++++++++ + src/lib/krb5/asn.1/asn1_k_encode.c | 14 ++++++++++++++ + src/lib/krb5/krb/kfree.c | 10 ++++++++++ + src/lib/krb5/libkrb5.exports | 3 +++ + src/tests/asn.1/krb5_decode_test.c | 18 ++++++++++++++++++ + src/tests/asn.1/krb5_encode_test.c | 8 ++++++++ + src/tests/asn.1/ktest.c | 23 ++++++++++++++++++++++ + src/tests/asn.1/ktest.h | 5 +++++ + src/tests/asn.1/ktest_equal.c | 12 ++++++++++++ + src/tests/asn.1/ktest_equal.h | 3 +++ + src/tests/asn.1/reference_encode.out | 1 + + src/tests/asn.1/trval_reference.out | 37 ++++++++++++++++++++++++++++++++++++ + 12 files changed, 147 insertions(+) + +diff --git a/src/include/k5-int.h b/src/include/k5-int.h +index 096cd14..8f039ee 100644 +--- a/src/include/k5-int.h ++++ b/src/include/k5-int.h +@@ -518,6 +518,12 @@ typedef struct _krb5_pa_otp_req { + krb5_data vendor; + } krb5_pa_otp_req; + ++typedef struct _krb5_kkdcp_message { ++ krb5_data kerb_message; ++ krb5_data target_domain; ++ krb5_int32 dclocator_hint; ++} krb5_kkdcp_message; ++ + #include + #include + +@@ -898,6 +904,7 @@ void k5_free_otp_tokeninfo(krb5_context context, krb5_otp_tokeninfo *val); + void k5_free_pa_otp_challenge(krb5_context context, + krb5_pa_otp_challenge *val); + void k5_free_pa_otp_req(krb5_context context, krb5_pa_otp_req *val); ++void k5_free_kkdcp_message(krb5_context context, krb5_kkdcp_message *val); + + /* #include "krb5/wordsize.h" -- comes in through base-defs.h. */ + #include "com_err.h" +@@ -1438,6 +1445,9 @@ encode_krb5_pa_otp_req(const krb5_pa_otp_req *, krb5_data **); + krb5_error_code + encode_krb5_pa_otp_enc_req(const krb5_data *, krb5_data **); + ++krb5_error_code ++encode_krb5_kkdcp_message(const krb5_kkdcp_message *, krb5_data **); ++ + /************************************************************************* + * End of prototypes for krb5_encode.c + *************************************************************************/ +@@ -1608,6 +1618,9 @@ decode_krb5_pa_otp_req(const krb5_data *, krb5_pa_otp_req **); + krb5_error_code + decode_krb5_pa_otp_enc_req(const krb5_data *, krb5_data **); + ++krb5_error_code ++decode_krb5_kkdcp_message(const krb5_data *, krb5_kkdcp_message **); ++ + struct _krb5_key_data; /* kdb.h */ + + struct ldap_seqof_key_data { +diff --git a/src/lib/krb5/asn.1/asn1_k_encode.c b/src/lib/krb5/asn.1/asn1_k_encode.c +index 7b9179d..4dc49c2 100644 +--- a/src/lib/krb5/asn.1/asn1_k_encode.c ++++ b/src/lib/krb5/asn.1/asn1_k_encode.c +@@ -1711,3 +1711,17 @@ static const struct atype_info *pa_otp_enc_req_fields[] = { + }; + DEFSEQTYPE(pa_otp_enc_req, krb5_data, pa_otp_enc_req_fields); + MAKE_CODEC(krb5_pa_otp_enc_req, pa_otp_enc_req); ++ ++DEFFIELD(kkdcp_message_0, krb5_kkdcp_message, ++ kerb_message, 0, ostring_data); ++DEFFIELD(kkdcp_message_1, krb5_kkdcp_message, ++ target_domain, 1, opt_gstring_data); ++DEFFIELD(kkdcp_message_2, krb5_kkdcp_message, ++ dclocator_hint, 2, opt_int32); ++static const struct atype_info *kkdcp_message_fields[] = { ++ &k5_atype_kkdcp_message_0, &k5_atype_kkdcp_message_1, ++ &k5_atype_kkdcp_message_2 ++}; ++DEFSEQTYPE(kkdcp_message, krb5_kkdcp_message, ++ kkdcp_message_fields); ++MAKE_CODEC(krb5_kkdcp_message, kkdcp_message); +diff --git a/src/lib/krb5/krb/kfree.c b/src/lib/krb5/krb/kfree.c +index 32b2151..f86c619 100644 +--- a/src/lib/krb5/krb/kfree.c ++++ b/src/lib/krb5/krb/kfree.c +@@ -821,3 +821,13 @@ k5_free_pa_otp_req(krb5_context context, krb5_pa_otp_req *val) + free(val->vendor.data); + free(val); + } ++ ++void ++k5_free_kkdcp_message(krb5_context context, krb5_kkdcp_message *val) ++{ ++ if (val == NULL) ++ return; ++ free(val->target_domain.data); ++ free(val->kerb_message.data); ++ free(val); ++} +diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports +index 863ec02..2d0852d 100644 +--- a/src/lib/krb5/libkrb5.exports ++++ b/src/lib/krb5/libkrb5.exports +@@ -25,6 +25,7 @@ decode_krb5_iakerb_finished + decode_krb5_iakerb_header + decode_krb5_kdc_req_body + decode_krb5_otp_tokeninfo ++decode_krb5_kkdcp_message + decode_krb5_pa_enc_ts + decode_krb5_pa_for_user + decode_krb5_pa_fx_fast_reply +@@ -72,6 +73,7 @@ encode_krb5_iakerb_finished + encode_krb5_iakerb_header + encode_krb5_kdc_req_body + encode_krb5_otp_tokeninfo ++encode_krb5_kkdcp_message + encode_krb5_pa_enc_ts + encode_krb5_pa_for_user + encode_krb5_pa_fx_fast_reply +@@ -113,6 +115,7 @@ k5_expand_path_tokens + k5_expand_path_tokens_extra + k5_free_algorithm_identifier + k5_free_otp_tokeninfo ++k5_free_kkdcp_message + k5_free_pa_otp_challenge + k5_free_pa_otp_req + k5_free_serverlist +diff --git a/src/tests/asn.1/krb5_decode_test.c b/src/tests/asn.1/krb5_decode_test.c +index 8719978..f12bb16 100644 +--- a/src/tests/asn.1/krb5_decode_test.c ++++ b/src/tests/asn.1/krb5_decode_test.c +@@ -54,6 +54,8 @@ static void ktest_free_reply_key_pack(krb5_context context, + static void ktest_free_reply_key_pack_draft9(krb5_context context, + krb5_reply_key_pack_draft9 *val); + #endif ++static void ktest_free_kkdcp_message(krb5_context context, ++ krb5_kkdcp_message *val); + + int main(argc, argv) + int argc; +@@ -1077,6 +1079,13 @@ int main(argc, argv) + ktest_empty_data(&ref); + } + ++ /****************************************************************/ ++ /* decode_krb5_kkdcp_message */ ++ { ++ setup(krb5_kkdcp_message,ktest_make_sample_kkdcp_message); ++ decode_run("kkdcp_message","","30 82 01 FC A0 82 01 EC 04 82 01 E8 6A 82 01 E4 30 82 01 E0 A1 03 02 01 05 A2 03 02 01 0A A3 26 30 24 30 10 A1 03 02 01 0D A2 09 04 07 70 61 2D 64 61 74 61 30 10 A1 03 02 01 0D A2 09 04 07 70 61 2D 64 61 74 61 A4 82 01 AA 30 82 01 A6 A0 07 03 05 00 FE DC BA 98 A1 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A2 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A3 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A4 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A5 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A6 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A7 03 02 01 2A A8 08 30 06 02 01 00 02 01 01 A9 20 30 1E 30 0D A0 03 02 01 02 A1 06 04 04 12 D0 00 23 30 0D A0 03 02 01 02 A1 06 04 04 12 D0 00 23 AA 25 30 23 A0 03 02 01 00 A1 03 02 01 05 A2 17 04 15 6B 72 62 41 53 4E 2E 31 20 74 65 73 74 20 6D 65 73 73 61 67 65 AB 81 BF 30 81 BC 61 5C 30 5A A0 03 02 01 05 A1 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A2 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A3 25 30 23 A0 03 02 01 00 A1 03 02 01 05 A2 17 04 15 6B 72 62 41 53 4E 2E 31 20 74 65 73 74 20 6D 65 73 73 61 67 65 61 5C 30 5A A0 03 02 01 05 A1 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A2 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A3 25 30 23 A0 03 02 01 00 A1 03 02 01 05 A2 17 04 15 6B 72 62 41 53 4E 2E 31 20 74 65 73 74 20 6D 65 73 73 61 67 65 A1 0A 1B 08 6B 72 62 35 64 61 74 61",decode_krb5_kkdcp_message,ktest_equal_kkdcp_message,ktest_free_kkdcp_message); ++ } ++ + #ifndef DISABLE_PKINIT + + /****************************************************************/ +@@ -1262,3 +1271,12 @@ ktest_free_reply_key_pack_draft9(krb5_context context, + } + + #endif /* not DISABLE_PKINIT */ ++ ++static void ++ktest_free_kkdcp_message(krb5_context context, ++ krb5_kkdcp_message *val) ++{ ++ if (val) ++ ktest_empty_kkdcp_message(val); ++ free(val); ++} +diff --git a/src/tests/asn.1/krb5_encode_test.c b/src/tests/asn.1/krb5_encode_test.c +index 638f6fe..3ba8684 100644 +--- a/src/tests/asn.1/krb5_encode_test.c ++++ b/src/tests/asn.1/krb5_encode_test.c +@@ -734,6 +734,14 @@ main(argc, argv) + encode_run(d, "pa_otp_enc_req", "", encode_krb5_pa_otp_enc_req); + ktest_empty_data(&d); + } ++ /****************************************************************/ ++ /* encode_krb5_kkdcp_message */ ++ { ++ krb5_kkdcp_message info; ++ ktest_make_sample_kkdcp_message(&info); ++ encode_run(info, "kkdcp_message", "", encode_krb5_kkdcp_message); ++ ktest_empty_kkdcp_message(&info); ++ } + #ifndef DISABLE_PKINIT + /****************************************************************/ + /* encode_krb5_pa_pk_as_req */ +diff --git a/src/tests/asn.1/ktest.c b/src/tests/asn.1/ktest.c +index aa41fd8..4ce9f70 100644 +--- a/src/tests/asn.1/ktest.c ++++ b/src/tests/asn.1/ktest.c +@@ -933,6 +933,21 @@ ktest_make_sample_ldap_seqof_key_data(ldap_seqof_key_data *p) + } + #endif + ++void ++ktest_make_sample_kkdcp_message(krb5_kkdcp_message *p) ++{ ++ krb5_kdc_req req; ++ krb5_data *message; ++ ++ ktest_make_sample_kdc_req(&req); ++ req.msg_type = KRB5_AS_REQ; ++ encode_krb5_as_req(&req, &message); ++ p->kerb_message = *message; ++ free(message); ++ ktest_empty_kdc_req(&req); ++ ktest_make_sample_data(&(p->target_domain)); ++ p->dclocator_hint = 0; ++} + + /****************************************************************/ + /* destructors */ +@@ -1731,3 +1746,11 @@ ktest_empty_ldap_seqof_key_data(krb5_context ctx, ldap_seqof_key_data *p) + free(p->key_data); + } + #endif ++ ++void ++ktest_empty_kkdcp_message(krb5_kkdcp_message *p) ++{ ++ ktest_empty_data(&p->kerb_message); ++ ktest_empty_data(&p->target_domain); ++ p->dclocator_hint = -1; ++} +diff --git a/src/tests/asn.1/ktest.h b/src/tests/asn.1/ktest.h +index 67a6c69..a9ebb77 100644 +--- a/src/tests/asn.1/ktest.h ++++ b/src/tests/asn.1/ktest.h +@@ -119,6 +119,9 @@ void ktest_make_sample_pkinit_supp_pub_info(krb5_pkinit_supp_pub_info *p); + #ifdef ENABLE_LDAP + void ktest_make_sample_ldap_seqof_key_data(ldap_seqof_key_data *p); + #endif ++ ++void ktest_make_sample_kkdcp_message(krb5_kkdcp_message *p); ++ + /*----------------------------------------------------------------------*/ + + void ktest_empty_authorization_data(krb5_authdata **ad); +@@ -200,6 +203,8 @@ void ktest_empty_pkinit_supp_pub_info(krb5_pkinit_supp_pub_info *p); + void ktest_empty_ldap_seqof_key_data(krb5_context, ldap_seqof_key_data *p); + #endif + ++void ktest_empty_kkdcp_message(krb5_kkdcp_message *p); ++ + extern krb5_context test_context; + extern char *sample_principal_name; + +diff --git a/src/tests/asn.1/ktest_equal.c b/src/tests/asn.1/ktest_equal.c +index 4e71242..39c35b5 100644 +--- a/src/tests/asn.1/ktest_equal.c ++++ b/src/tests/asn.1/ktest_equal.c +@@ -1039,3 +1039,15 @@ ktest_equal_reply_key_pack_draft9(krb5_reply_key_pack_draft9 *ref, + } + + #endif /* not DISABLE_PKINIT */ ++ ++int ++ktest_equal_kkdcp_message(krb5_kkdcp_message *ref, krb5_kkdcp_message *var) ++{ ++ int p = TRUE; ++ if (ref == var) return TRUE; ++ else if (ref == NULL || var == NULL) return FALSE; ++ p = p && data_eq(ref->kerb_message, var->kerb_message); ++ p = p && data_eq(ref->target_domain, var->target_domain); ++ p = p && (ref->dclocator_hint == var->dclocator_hint); ++ return p; ++} +diff --git a/src/tests/asn.1/ktest_equal.h b/src/tests/asn.1/ktest_equal.h +index e75f86a..491653f 100644 +--- a/src/tests/asn.1/ktest_equal.h ++++ b/src/tests/asn.1/ktest_equal.h +@@ -145,4 +145,7 @@ generic(ktest_equal_reply_key_pack, krb5_reply_key_pack); + generic(ktest_equal_reply_key_pack_draft9, krb5_reply_key_pack_draft9); + #endif /* not DISABLE_PKINIT */ + ++int ktest_equal_kkdcp_message(krb5_kkdcp_message *ref, ++ krb5_kkdcp_message *var); ++ + #endif +diff --git a/src/tests/asn.1/reference_encode.out b/src/tests/asn.1/reference_encode.out +index 315e25b..b737da3 100644 +--- a/src/tests/asn.1/reference_encode.out ++++ b/src/tests/asn.1/reference_encode.out +@@ -68,3 +68,4 @@ encode_krb5_pa_otp_challenge: 30 81 A5 80 08 6D 61 78 6E 6F 6E 63 65 81 0B 74 65 + encode_krb5_pa_otp_req(optionals NULL): 30 2C 80 05 00 00 00 00 00 A2 23 A0 03 02 01 00 A1 03 02 01 05 A2 17 04 15 6B 72 62 41 53 4E 2E 31 20 74 65 73 74 20 6D 65 73 73 61 67 65 + encode_krb5_pa_otp_req: 30 81 B9 80 05 00 60 00 00 00 81 05 6E 6F 6E 63 65 A2 23 A0 03 02 01 00 A1 03 02 01 05 A2 17 04 15 6B 72 62 41 53 4E 2E 31 20 74 65 73 74 20 6D 65 73 73 61 67 65 A3 0B 06 09 60 86 48 01 65 03 04 02 01 84 02 03 E8 85 05 66 72 6F 67 73 86 0A 6D 79 66 69 72 73 74 70 69 6E 87 05 68 61 72 6B 21 88 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A 89 03 33 34 36 8A 01 02 8B 09 79 6F 75 72 74 6F 6B 65 6E 8C 28 75 72 6E 3A 69 65 74 66 3A 70 61 72 61 6D 73 3A 78 6D 6C 3A 6E 73 3A 6B 65 79 70 72 6F 76 3A 70 73 6B 63 3A 68 6F 74 70 8D 0B 45 78 61 6D 70 6C 65 63 6F 72 70 + encode_krb5_pa_otp_enc_req: 30 0A 80 08 6B 72 62 35 64 61 74 61 ++encode_krb5_kkdcp_message: 30 82 01 FC A0 82 01 EC 04 82 01 E8 6A 82 01 E4 30 82 01 E0 A1 03 02 01 05 A2 03 02 01 0A A3 26 30 24 30 10 A1 03 02 01 0D A2 09 04 07 70 61 2D 64 61 74 61 30 10 A1 03 02 01 0D A2 09 04 07 70 61 2D 64 61 74 61 A4 82 01 AA 30 82 01 A6 A0 07 03 05 00 FE DC BA 98 A1 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A2 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A3 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A4 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A5 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A6 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A7 03 02 01 2A A8 08 30 06 02 01 00 02 01 01 A9 20 30 1E 30 0D A0 03 02 01 02 A1 06 04 04 12 D0 00 23 30 0D A0 03 02 01 02 A1 06 04 04 12 D0 00 23 AA 25 30 23 A0 03 02 01 00 A1 03 02 01 05 A2 17 04 15 6B 72 62 41 53 4E 2E 31 20 74 65 73 74 20 6D 65 73 73 61 67 65 AB 81 BF 30 81 BC 61 5C 30 5A A0 03 02 01 05 A1 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A2 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A3 25 30 23 A0 03 02 01 00 A1 03 02 01 05 A2 17 04 15 6B 72 62 41 53 4E 2E 31 20 74 65 73 74 20 6D 65 73 73 61 67 65 61 5C 30 5A A0 03 02 01 05 A1 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A2 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A3 25 30 23 A0 03 02 01 00 A1 03 02 01 05 A2 17 04 15 6B 72 62 41 53 4E 2E 31 20 74 65 73 74 20 6D 65 73 73 61 67 65 A1 0A 1B 08 6B 72 62 35 64 61 74 61 +diff --git a/src/tests/asn.1/trval_reference.out b/src/tests/asn.1/trval_reference.out +index 461021e..599580c 100644 +--- a/src/tests/asn.1/trval_reference.out ++++ b/src/tests/asn.1/trval_reference.out +@@ -1478,3 +1478,40 @@ encode_krb5_pa_otp_enc_req: + [Sequence/Sequence Of] + . [0] <8> + 6b 72 62 35 64 61 74 61 krb5data ++ ++encode_krb5_kkdcp_message: ++ ++[Sequence/Sequence Of] ++. [0] [Octet String] <488> ++ 6a 82 01 e4 30 82 01 e0 a1 03 02 01 05 a2 03 02 j...0........... ++ 01 0a a3 26 30 24 30 10 a1 03 02 01 0d a2 09 04 ...&0$0......... ++ 07 70 61 2d 64 61 74 61 30 10 a1 03 02 01 0d a2 .pa-data0....... ++ 09 04 07 70 61 2d 64 61 74 61 a4 82 01 aa 30 82 ...pa-data....0. ++ 01 a6 a0 07 03 05 00 fe dc ba 98 a1 1a 30 18 a0 .............0.. ++ 03 02 01 01 a1 11 30 0f 1b 06 68 66 74 73 61 69 ......0...hftsai ++ 1b 05 65 78 74 72 61 a2 10 1b 0e 41 54 48 45 4e ..extra....ATHEN ++ 41 2e 4d 49 54 2e 45 44 55 a3 1a 30 18 a0 03 02 A.MIT.EDU..0.... ++ 01 01 a1 11 30 0f 1b 06 68 66 74 73 61 69 1b 05 ....0...hftsai.. ++ 65 78 74 72 61 a4 11 18 0f 31 39 39 34 30 36 31 extra....1994061 ++ 30 30 36 30 33 31 37 5a a5 11 18 0f 31 39 39 34 0060317Z....1994 ++ 30 36 31 30 30 36 30 33 31 37 5a a6 11 18 0f 31 0610060317Z....1 ++ 39 39 34 30 36 31 30 30 36 30 33 31 37 5a a7 03 9940610060317Z.. ++ 02 01 2a a8 08 30 06 02 01 00 02 01 01 a9 20 30 ..*..0........ 0 ++ 1e 30 0d a0 03 02 01 02 a1 06 04 04 12 d0 00 23 .0.............# ++ 30 0d a0 03 02 01 02 a1 06 04 04 12 d0 00 23 aa 0.............#. ++ 25 30 23 a0 03 02 01 00 a1 03 02 01 05 a2 17 04 %0#............. ++ 15 6b 72 62 41 53 4e 2e 31 20 74 65 73 74 20 6d .krbASN.1 test m ++ 65 73 73 61 67 65 ab 81 bf 30 81 bc 61 5c 30 5a essage...0..a\0Z ++ a0 03 02 01 05 a1 10 1b 0e 41 54 48 45 4e 41 2e .........ATHENA. ++ 4d 49 54 2e 45 44 55 a2 1a 30 18 a0 03 02 01 01 MIT.EDU..0...... ++ a1 11 30 0f 1b 06 68 66 74 73 61 69 1b 05 65 78 ..0...hftsai..ex ++ 74 72 61 a3 25 30 23 a0 03 02 01 00 a1 03 02 01 tra.%0#......... ++ 05 a2 17 04 15 6b 72 62 41 53 4e 2e 31 20 74 65 .....krbASN.1 te ++ 73 74 20 6d 65 73 73 61 67 65 61 5c 30 5a a0 03 st messagea\0Z.. ++ 02 01 05 a1 10 1b 0e 41 54 48 45 4e 41 2e 4d 49 .......ATHENA.MI ++ 54 2e 45 44 55 a2 1a 30 18 a0 03 02 01 01 a1 11 T.EDU..0........ ++ 30 0f 1b 06 68 66 74 73 61 69 1b 05 65 78 74 72 0...hftsai..extr ++ 61 a3 25 30 23 a0 03 02 01 00 a1 03 02 01 05 a2 a.%0#........... ++ 17 04 15 6b 72 62 41 53 4e 2e 31 20 74 65 73 74 ...krbASN.1 test ++ 20 6d 65 73 73 61 67 65 message ++. [1] [General string] "krb5data" +-- +2.1.0 + diff --git a/0006-Dispatch-style-protocol-switching-for-transport.patch b/0006-Dispatch-style-protocol-switching-for-transport.patch new file mode 100644 index 0000000..e36544e --- /dev/null +++ b/0006-Dispatch-style-protocol-switching-for-transport.patch @@ -0,0 +1,506 @@ +From 606e2ccc0a2546a23761f910482a55c5bf0f98ac Mon Sep 17 00:00:00 2001 +From: "Robbie Harwood (frozencemetery)" +Date: Fri, 16 Aug 2013 14:48:55 -0400 +Subject: [PATCH 06/13] Dispatch-style protocol switching for transport + +Switch to using per-transport-type functions when a socket that we're +using to communicate with a server becomes readable or writable, and add +them as pointers to the connection state. The functions are passed the +name of the realm of the server being contacted, as we expect to need +this in the near future. + +[nalin@redhat.com: replace macros with typedefs] +[nalin@redhat.com: compare transports with TCP_OR_UDP rather than with 0] + +ticket: 7929 +--- + src/lib/krb5/os/changepw.c | 6 +- + src/lib/krb5/os/os-proto.h | 1 + + src/lib/krb5/os/sendto_kdc.c | 297 ++++++++++++++++++++++++------------------- + 3 files changed, 171 insertions(+), 133 deletions(-) + +diff --git a/src/lib/krb5/os/changepw.c b/src/lib/krb5/os/changepw.c +index a1c9885..0ee427d 100644 +--- a/src/lib/krb5/os/changepw.c ++++ b/src/lib/krb5/os/changepw.c +@@ -261,9 +261,9 @@ change_set_password(krb5_context context, + callback_info.pfn_cleanup = kpasswd_sendto_msg_cleanup; + krb5_free_data_contents(callback_ctx.context, &chpw_rep); + +- code = k5_sendto(callback_ctx.context, NULL, &sl, strategy, +- &callback_info, &chpw_rep, ss2sa(&remote_addr), +- &addrlen, NULL, NULL, NULL); ++ code = k5_sendto(callback_ctx.context, NULL, &creds->server->realm, ++ &sl, strategy, &callback_info, &chpw_rep, ++ ss2sa(&remote_addr), &addrlen, NULL, NULL, NULL); + if (code) { + /* + * Here we may want to switch to TCP on some errors. +diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h +index f23dda5..e60ccd0 100644 +--- a/src/lib/krb5/os/os-proto.h ++++ b/src/lib/krb5/os/os-proto.h +@@ -115,6 +115,7 @@ int _krb5_use_dns_kdc (krb5_context); + int _krb5_conf_boolean (const char *); + + krb5_error_code k5_sendto(krb5_context context, const krb5_data *message, ++ const krb5_data *realm, + const struct serverlist *addrs, + k5_transport_strategy strategy, + struct sendto_callback_info *callback_info, +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index c6aae8e..28f1c4d 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -96,11 +96,18 @@ struct outgoing_message { + unsigned char msg_len_buf[4]; + }; + ++struct conn_state; ++typedef krb5_boolean fd_handler_fn(krb5_context context, ++ const krb5_data *realm, ++ struct conn_state *conn, ++ struct select_state *selstate); ++ + struct conn_state { + SOCKET fd; + enum conn_states state; +- int (*service)(krb5_context context, struct conn_state *, +- struct select_state *, int); ++ fd_handler_fn *service_connect; ++ fd_handler_fn *service_write; ++ fd_handler_fn *service_read; + struct remote_address addr; + struct incoming_message in; + struct outgoing_message out; +@@ -409,9 +416,9 @@ krb5_sendto_kdc(krb5_context context, const krb5_data *message, + return retval; + + err = 0; +- retval = k5_sendto(context, message, &servers, strategy, NULL, reply, +- NULL, NULL, &server_used, check_for_svc_unavailable, +- &err); ++ retval = k5_sendto(context, message, realm, &servers, strategy, NULL, ++ reply, NULL, NULL, &server_used, ++ check_for_svc_unavailable, &err); + if (retval == KRB5_KDC_UNREACH) { + if (err == KDC_ERR_SVC_UNAVAILABLE) { + retval = KRB5KDC_ERR_SVC_UNAVAILABLE; +@@ -457,10 +464,10 @@ cleanup: + * connections already in progress + */ + +-static int service_tcp_fd(krb5_context context, struct conn_state *conn, +- struct select_state *selstate, int ssflags); +-static int service_udp_fd(krb5_context context, struct conn_state *conn, +- struct select_state *selstate, int ssflags); ++static fd_handler_fn service_tcp_connect; ++static fd_handler_fn service_tcp_write; ++static fd_handler_fn service_tcp_read; ++static fd_handler_fn service_udp_read; + + /* Set up the actual message we will send across the underlying transport to + * communicate the payload message, using one or both of state->out.sgbuf. */ +@@ -505,9 +512,13 @@ add_connection(struct conn_state **conns, k5_transport transport, + state->server_index = server_index; + SG_SET(&state->out.sgbuf[1], NULL, 0); + if (transport == TCP) { +- state->service = service_tcp_fd; ++ state->service_connect = service_tcp_connect; ++ state->service_write = service_tcp_write; ++ state->service_read = service_tcp_read; + } else { +- state->service = service_udp_fd; ++ state->service_connect = NULL; ++ state->service_write = NULL; ++ state->service_read = service_udp_read; + + if (*udpbufp == NULL) { + *udpbufp = malloc(MAX_DGRAM_SIZE); +@@ -788,9 +799,13 @@ maybe_send(krb5_context context, struct conn_state *conn, + } + + static void +-kill_conn(struct conn_state *conn, struct select_state *selstate) ++kill_conn(krb5_context context, struct conn_state *conn, ++ struct select_state *selstate) + { ++ if (socktype_for_transport(conn->addr.transport) == SOCK_STREAM) ++ TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr); + cm_remove_fd(selstate, conn->fd); ++ + closesocket(conn->fd); + conn->fd = INVALID_SOCKET; + conn->state = FAILED; +@@ -814,136 +829,157 @@ get_so_error(int fd) + return sockerr; + } + +-/* Process events on a TCP socket. Return 1 if we get a complete reply. */ +-static int +-service_tcp_fd(krb5_context context, struct conn_state *conn, +- struct select_state *selstate, int ssflags) ++/* Perform next step in sending. Return true on usable data. */ ++static krb5_boolean ++service_dispatch(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate, ++ int ssflags) + { +- int e = 0; +- ssize_t nwritten, nread; +- SOCKET_WRITEV_TEMP tmp; +- struct incoming_message *in = &conn->in; +- struct outgoing_message *out = &conn->out; +- + /* Check for a socket exception. */ +- if (ssflags & SSF_EXCEPTION) +- goto kill_conn; ++ if (ssflags & SSF_EXCEPTION) { ++ kill_conn(context, conn, selstate); ++ return FALSE; ++ } + + switch (conn->state) { + case CONNECTING: +- /* Check whether the connection succeeded. */ +- e = get_so_error(conn->fd); +- if (e) { +- TRACE_SENDTO_KDC_TCP_ERROR_CONNECT(context, &conn->addr, e); +- goto kill_conn; +- } +- conn->state = WRITING; ++ assert(conn->service_connect != NULL); ++ return conn->service_connect(context, realm, conn, selstate); ++ case WRITING: ++ assert(conn->service_write != NULL); ++ return conn->service_write(context, realm, conn, selstate); ++ case READING: ++ assert(conn->service_read != NULL); ++ return conn->service_read(context, realm, conn, selstate); ++ default: ++ abort(); ++ } ++} + +- /* Record this connection's timeout for service_fds. */ +- if (get_curtime_ms(&conn->endtime) == 0) +- conn->endtime += 10000; ++/* Initialize TCP transport. */ ++static krb5_boolean ++service_tcp_connect(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) ++{ ++ /* Check whether the connection succeeded. */ ++ int e = get_so_error(conn->fd); + +- /* Fall through. */ +- case WRITING: +- TRACE_SENDTO_KDC_TCP_SEND(context, &conn->addr); +- nwritten = SOCKET_WRITEV(conn->fd, out->sgp, out->sg_count, tmp); +- if (nwritten < 0) { +- TRACE_SENDTO_KDC_TCP_ERROR_SEND(context, &conn->addr, +- SOCKET_ERRNO); +- goto kill_conn; ++ if (e) { ++ TRACE_SENDTO_KDC_TCP_ERROR_CONNECT(context, &conn->addr, e); ++ kill_conn(context, conn, selstate); ++ return FALSE; ++ } ++ ++ conn->state = WRITING; ++ ++ /* Record this connection's timeout for service_fds. */ ++ if (get_curtime_ms(&conn->endtime) == 0) ++ conn->endtime += 10000; ++ ++ return service_tcp_write(context, realm, conn, selstate); ++} ++ ++/* Sets conn->state to READING when done. */ ++static krb5_boolean ++service_tcp_write(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) ++{ ++ ssize_t nwritten; ++ SOCKET_WRITEV_TEMP tmp; ++ ++ TRACE_SENDTO_KDC_TCP_SEND(context, &conn->addr); ++ nwritten = SOCKET_WRITEV(conn->fd, conn->out.sgp, conn->out.sg_count, tmp); ++ if (nwritten < 0) { ++ TRACE_SENDTO_KDC_TCP_ERROR_SEND(context, &conn->addr, SOCKET_ERRNO); ++ kill_conn(context, conn, selstate); ++ return FALSE; ++ } ++ while (nwritten) { ++ sg_buf *sgp = conn->out.sgp; ++ if ((size_t)nwritten < SG_LEN(sgp)) { ++ SG_ADVANCE(sgp, (size_t)nwritten); ++ nwritten = 0; ++ } else { ++ nwritten -= SG_LEN(sgp); ++ conn->out.sgp++; ++ conn->out.sg_count--; + } +- while (nwritten) { +- sg_buf *sgp = out->sgp; +- if ((size_t) nwritten < SG_LEN(sgp)) { +- SG_ADVANCE(sgp, (size_t) nwritten); +- nwritten = 0; +- } else { +- nwritten -= SG_LEN(sgp); +- out->sgp++; +- out->sg_count--; +- } ++ } ++ if (conn->out.sg_count == 0) { ++ /* Done writing, switch to reading. */ ++ cm_read(selstate, conn->fd); ++ conn->state = READING; ++ } ++ return FALSE; ++} ++ ++/* Return true on usable data. */ ++static krb5_boolean ++service_tcp_read(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) ++{ ++ ssize_t nread; ++ int e = 0; ++ struct incoming_message *in = &conn->in; ++ ++ if (in->bufsizebytes_read == 4) { ++ /* Reading data. */ ++ nread = SOCKET_READ(conn->fd, &in->buf[in->pos], in->n_left); ++ if (nread <= 0) { ++ e = nread ? SOCKET_ERRNO : ECONNRESET; ++ TRACE_SENDTO_KDC_TCP_ERROR_RECV(context, &conn->addr, e); ++ kill_conn(context, conn, selstate); ++ return FALSE; + } +- if (out->sg_count == 0) { +- /* Done writing, switch to reading. */ +- cm_read(selstate, conn->fd); +- conn->state = READING; +- in->bufsizebytes_read = 0; +- in->bufsize = 0; +- in->pos = 0; +- in->buf = NULL; +- in->n_left = 0; ++ in->n_left -= nread; ++ in->pos += nread; ++ if (in->n_left <= 0) ++ return TRUE; ++ } else { ++ /* Reading length. */ ++ nread = SOCKET_READ(conn->fd, in->bufsizebytes + in->bufsizebytes_read, ++ 4 - in->bufsizebytes_read); ++ if (nread <= 0) { ++ e = nread ? SOCKET_ERRNO : ECONNRESET; ++ TRACE_SENDTO_KDC_TCP_ERROR_RECV_LEN(context, &conn->addr, e); ++ kill_conn(context, conn, selstate); ++ return FALSE; + } +- return 0; +- +- case READING: ++ in->bufsizebytes_read += nread; + if (in->bufsizebytes_read == 4) { +- /* Reading data. */ +- nread = SOCKET_READ(conn->fd, &in->buf[in->pos], in->n_left); +- if (nread <= 0) { +- e = nread ? SOCKET_ERRNO : ECONNRESET; +- TRACE_SENDTO_KDC_TCP_ERROR_RECV(context, &conn->addr, e); +- goto kill_conn; +- } +- in->n_left -= nread; +- in->pos += nread; +- if (in->n_left <= 0) +- return 1; +- } else { +- /* Reading length. */ +- nread = SOCKET_READ(conn->fd, +- in->bufsizebytes + in->bufsizebytes_read, +- 4 - in->bufsizebytes_read); +- if (nread <= 0) { +- e = nread ? SOCKET_ERRNO : ECONNRESET; +- TRACE_SENDTO_KDC_TCP_ERROR_RECV_LEN(context, &conn->addr, e); +- goto kill_conn; ++ unsigned long len = load_32_be(in->bufsizebytes); ++ /* Arbitrary 1M cap. */ ++ if (len > 1 * 1024 * 1024) { ++ kill_conn(context, conn, selstate); ++ return FALSE; + } +- in->bufsizebytes_read += nread; +- if (in->bufsizebytes_read == 4) { +- unsigned long len = load_32_be(in->bufsizebytes); +- /* Arbitrary 1M cap. */ +- if (len > 1 * 1024 * 1024) +- goto kill_conn; +- in->bufsize = in->n_left = len; +- in->pos = 0; +- in->buf = malloc(len); +- if (in->buf == NULL) +- goto kill_conn; ++ in->bufsize = in->n_left = len; ++ in->pos = 0; ++ in->buf = malloc(len); ++ if (in->buf == NULL) { ++ kill_conn(context, conn, selstate); ++ return FALSE; + } + } +- break; +- +- default: +- abort(); + } +- return 0; +- +-kill_conn: +- TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr); +- kill_conn(conn, selstate); +- return 0; ++ return FALSE; + } + +-/* Process events on a UDP socket. Return 1 if we get a reply. */ +-static int +-service_udp_fd(krb5_context context, struct conn_state *conn, +- struct select_state *selstate, int ssflags) ++/* Process events on a UDP socket. Return true if we get a reply. */ ++static krb5_boolean ++service_udp_read(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) + { + int nread; + +- if (!(ssflags & (SSF_READ|SSF_EXCEPTION))) +- abort(); +- if (conn->state != READING) +- abort(); +- + nread = recv(conn->fd, conn->in.buf, conn->in.bufsize, 0); + if (nread < 0) { + TRACE_SENDTO_KDC_UDP_ERROR_RECV(context, &conn->addr, SOCKET_ERRNO); +- kill_conn(conn, selstate); +- return 0; ++ kill_conn(context, conn, selstate); ++ return FALSE; + } + conn->in.pos = nread; +- return 1; ++ return TRUE; + } + + /* Return the maximum of endtime and the endtime fields of all currently active +@@ -965,7 +1001,7 @@ get_endtime(time_ms endtime, struct conn_state *conns) + static krb5_boolean + service_fds(krb5_context context, struct select_state *selstate, + time_ms interval, struct conn_state *conns, +- struct select_state *seltemp, ++ struct select_state *seltemp, const krb5_data *realm, + int (*msg_handler)(krb5_context, const krb5_data *, void *), + void *msg_handler_data, struct conn_state **winner_out) + { +@@ -977,7 +1013,7 @@ service_fds(krb5_context context, struct select_state *selstate, + + e = get_curtime_ms(&endtime); + if (e) +- return 1; ++ return TRUE; + endtime += interval; + + e = 0; +@@ -991,7 +1027,7 @@ service_fds(krb5_context context, struct select_state *selstate, + + if (selret == 0) + /* Timeout, return to caller. */ +- return 0; ++ return FALSE; + + /* Got something on a socket, process it. */ + for (state = conns; state != NULL; state = state->next) { +@@ -1003,7 +1039,7 @@ service_fds(krb5_context context, struct select_state *selstate, + if (!ssflags) + continue; + +- if (state->service(context, state, selstate, ssflags)) { ++ if (service_dispatch(context, realm, state, selstate, ssflags)) { + int stop = 1; + + if (msg_handler != NULL) { +@@ -1014,14 +1050,14 @@ service_fds(krb5_context context, struct select_state *selstate, + + if (stop) { + *winner_out = state; +- return 1; ++ return TRUE; + } + } + } + } + if (e != 0) +- return 1; +- return 0; ++ return TRUE; ++ return FALSE; + } + + /* +@@ -1052,7 +1088,8 @@ service_fds(krb5_context context, struct select_state *selstate, + + krb5_error_code + k5_sendto(krb5_context context, const krb5_data *message, +- const struct serverlist *servers, k5_transport_strategy strategy, ++ const krb5_data *realm, const struct serverlist *servers, ++ k5_transport_strategy strategy, + struct sendto_callback_info* callback_info, krb5_data *reply, + struct sockaddr *remoteaddr, socklen_t *remoteaddrlen, + int *server_used, +@@ -1098,7 +1135,7 @@ k5_sendto(krb5_context context, const krb5_data *message, + if (maybe_send(context, state, message, sel_state, callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, +- msg_handler, msg_handler_data, &winner); ++ realm, msg_handler, msg_handler_data, &winner); + } + } + +@@ -1110,13 +1147,13 @@ k5_sendto(krb5_context context, const krb5_data *message, + if (maybe_send(context, state, message, sel_state, callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, +- msg_handler, msg_handler_data, &winner); ++ realm, msg_handler, msg_handler_data, &winner); + } + + /* Wait for two seconds at the end of the first pass. */ + if (!done) { + done = service_fds(context, sel_state, 2000, conns, seltemp, +- msg_handler, msg_handler_data, &winner); ++ realm, msg_handler, msg_handler_data, &winner); + } + + /* Make remaining passes over all of the connections. */ +@@ -1126,14 +1163,14 @@ k5_sendto(krb5_context context, const krb5_data *message, + if (maybe_send(context, state, message, sel_state, callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, +- msg_handler, msg_handler_data, &winner); ++ realm, msg_handler, msg_handler_data, &winner); + if (sel_state->nfds == 0) + break; + } + /* Wait for the delay backoff at the end of this pass. */ + if (!done) { + done = service_fds(context, sel_state, delay, conns, seltemp, +- msg_handler, msg_handler_data, &winner); ++ realm, msg_handler, msg_handler_data, &winner); + } + if (sel_state->nfds == 0) + break; +-- +2.1.0 + diff --git a/0007-HTTPS-transport-Microsoft-KKDCPP-implementation.patch b/0007-HTTPS-transport-Microsoft-KKDCPP-implementation.patch new file mode 100644 index 0000000..cd21dac --- /dev/null +++ b/0007-HTTPS-transport-Microsoft-KKDCPP-implementation.patch @@ -0,0 +1,858 @@ +From d950809ff49e3e7603594186d77135a09ab6b1b2 Mon Sep 17 00:00:00 2001 +From: Nalin Dahyabhai +Date: Thu, 24 Apr 2014 16:30:56 -0400 +Subject: [PATCH 07/13] HTTPS transport (Microsoft KKDCPP implementation) + +Add an 'HTTPS' transport type which connects to an [MS-KKDCP] proxy +server using HTTPS to communicate with a KDC. The KDC's name should +take the form of an HTTPS URL (e.g. "https://proxybox/KdcProxy"). + +An HTTPS connection's encryption layer can be reading and writing when +the application layer is expecting to write and read, so the HTTPS +callbacks have to handle being called multiple times. + +[nalin@redhat.com: use cleanup labels, make sure we always send the + realm name, keep a copy of the URI on-hand, move most of the + conditionally-compiled sections into their own conditionally-built + functions, break out HTTPS request formatting into a helper function, + handle the MS-KKDCP length bytes, update comments to mention specific + versions of the MS-KKDCP spec, differentiate TCP and HTTP trace + messages, trace unparseable responses] + +ticket: 7929 +--- + src/include/k5-trace.h | 13 ++ + src/lib/krb5/os/locate_kdc.c | 63 ++++++- + src/lib/krb5/os/os-proto.h | 2 + + src/lib/krb5/os/sendto_kdc.c | 417 ++++++++++++++++++++++++++++++++++++++--- + src/lib/krb5/os/t_locate_kdc.c | 2 + + src/lib/krb5/os/trace.c | 2 + + 6 files changed, 471 insertions(+), 28 deletions(-) + +diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h +index dfd34f6..f0d79f1 100644 +--- a/src/include/k5-trace.h ++++ b/src/include/k5-trace.h +@@ -312,6 +312,9 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); + TRACE(c, "AP-REQ ticket: {princ} -> {princ}, session key {keyblock}", \ + client, server, keyblock) + ++#define TRACE_SENDTO_KDC_ERROR_SET_MESSAGE(c, raddr, err) \ ++ TRACE(c, "Error preparing message to send to {raddr}: {errno}", \ ++ raddr, err) + #define TRACE_SENDTO_KDC(c, len, rlm, master, tcp) \ + TRACE(c, "Sending request ({int} bytes) to {data}{str}{str}", len, \ + rlm, (master) ? " (master)" : "", (tcp) ? " (tcp only)" : "") +@@ -321,6 +324,16 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); + TRACE(c, "Resolving hostname {str}", hostname) + #define TRACE_SENDTO_KDC_RESPONSE(c, len, raddr) \ + TRACE(c, "Received answer ({int} bytes) from {raddr}", len, raddr) ++#define TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(c, raddr) \ ++ TRACE(c, "HTTPS error connecting to {raddr}", raddr) ++#define TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(c, raddr, err) \ ++ TRACE(c, "HTTPS error receiving from {raddr}: {errno}", raddr, err) ++#define TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(c, raddr) \ ++ TRACE(c, "HTTPS error sending to {raddr}", raddr) ++#define TRACE_SENDTO_KDC_HTTPS_SEND(c, raddr) \ ++ TRACE(c, "Sending HTTPS request to {raddr}", raddr) ++#define TRACE_SENDTO_KDC_HTTPS_ERROR(c, errs) \ ++ TRACE(c, "HTTPS error: {str}", errs) + #define TRACE_SENDTO_KDC_TCP_CONNECT(c, raddr) \ + TRACE(c, "Initiating TCP connection to {raddr}", raddr) + #define TRACE_SENDTO_KDC_TCP_DISCONNECT(c, raddr) \ +diff --git a/src/lib/krb5/os/locate_kdc.c b/src/lib/krb5/os/locate_kdc.c +index 4c8aead..1136809 100644 +--- a/src/lib/krb5/os/locate_kdc.c ++++ b/src/lib/krb5/os/locate_kdc.c +@@ -91,8 +91,10 @@ k5_free_serverlist (struct serverlist *list) + { + size_t i; + +- for (i = 0; i < list->nservers; i++) ++ for (i = 0; i < list->nservers; i++) { + free(list->servers[i].hostname); ++ free(list->servers[i].uri_path); ++ } + free(list->servers); + list->servers = NULL; + list->nservers = 0; +@@ -140,6 +142,7 @@ add_addr_to_list(struct serverlist *list, k5_transport transport, int family, + entry->transport = transport; + entry->family = family; + entry->hostname = NULL; ++ entry->uri_path = NULL; + entry->addrlen = addrlen; + memcpy(&entry->addr, addr, addrlen); + list->nservers++; +@@ -149,7 +152,7 @@ add_addr_to_list(struct serverlist *list, k5_transport transport, int family, + /* Add a hostname entry to list. */ + static int + add_host_to_list(struct serverlist *list, const char *hostname, int port, +- k5_transport transport, int family) ++ k5_transport transport, int family, char *uri_path) + { + struct server_entry *entry; + +@@ -160,11 +163,46 @@ add_host_to_list(struct serverlist *list, const char *hostname, int port, + entry->family = family; + entry->hostname = strdup(hostname); + if (entry->hostname == NULL) +- return ENOMEM; ++ goto oom; ++ if (uri_path != NULL) { ++ entry->uri_path = strdup(uri_path); ++ if (entry->uri_path == NULL) ++ goto oom; ++ } + entry->port = port; + list->nservers++; + return 0; ++oom: ++ free(entry->hostname); ++ entry->hostname = NULL; ++ return ENOMEM; ++} ++ ++#ifdef PROXY_TLS_IMPL_OPENSSL ++static void ++parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host, ++ char **uri_path) ++{ ++ char *cp; ++ ++ if (strncmp(host_or_uri, "https://", 8) == 0) { ++ *transport = HTTPS; ++ *host = host_or_uri + 8; ++ ++ cp = strchr(*host, '/'); ++ if (cp != NULL) { ++ *cp = '\0'; ++ *uri_path = cp + 1; ++ } ++ } ++} ++#else ++static void ++parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host, ++ char **uri) ++{ + } ++#endif + + /* Return true if server is identical to an entry in list. */ + static krb5_boolean +@@ -222,9 +260,14 @@ locate_srv_conf_1(krb5_context context, const krb5_data *realm, + + for (i=0; hostlist[i]; i++) { + int p1, p2; ++ k5_transport this_transport = transport; ++ char *uri_path = NULL; + + host = hostlist[i]; + Tprintf ("entry %d is '%s'\n", i, host); ++ ++ parse_uri_if_https(host, &this_transport, &host, &uri_path); ++ + /* Find port number, and strip off any excess characters. */ + if (*host == '[' && (cp = strchr(host, ']'))) + cp = cp + 1; +@@ -244,6 +287,9 @@ locate_srv_conf_1(krb5_context context, const krb5_data *realm, + return EINVAL; + p1 = htons (l); + p2 = 0; ++ } else if (this_transport == HTTPS) { ++ p1 = htons(443); ++ p2 = 0; + } else { + p1 = udpport; + p2 = sec_udpport; +@@ -255,12 +301,15 @@ locate_srv_conf_1(krb5_context context, const krb5_data *realm, + *cp = '\0'; + } + +- code = add_host_to_list(serverlist, host, p1, transport, AF_UNSPEC); ++ code = add_host_to_list(serverlist, host, p1, this_transport, ++ AF_UNSPEC, uri_path); + /* Second port is for IPv4 UDP only, and should possibly go away as + * it was originally a krb4 compatibility measure. */ + if (code == 0 && p2 != 0 && +- (transport == TCP_OR_UDP || transport == UDP)) +- code = add_host_to_list(serverlist, host, p2, UDP, AF_INET); ++ (this_transport == TCP_OR_UDP || this_transport == UDP)) { ++ code = add_host_to_list(serverlist, host, p2, UDP, AF_INET, ++ uri_path); ++ } + if (code) + goto cleanup; + } +@@ -313,7 +362,7 @@ locate_srv_dns_1(const krb5_data *realm, const char *service, + for (entry = head; entry != NULL; entry = entry->next) { + transport = (strcmp(protocol, "_tcp") == 0) ? TCP : UDP; + code = add_host_to_list(serverlist, entry->host, htons(entry->port), +- transport, AF_UNSPEC); ++ transport, AF_UNSPEC, NULL); + if (code) + goto cleanup; + } +diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h +index e60ccd0..34bf028 100644 +--- a/src/lib/krb5/os/os-proto.h ++++ b/src/lib/krb5/os/os-proto.h +@@ -42,6 +42,7 @@ typedef enum { + TCP_OR_UDP = 0, + TCP, + UDP, ++ HTTPS, + } k5_transport; + + typedef enum { +@@ -55,6 +56,7 @@ struct server_entry { + char *hostname; /* NULL -> use addrlen/addr instead */ + int port; /* Used only if hostname set */ + k5_transport transport; /* May be 0 for UDP/TCP if hostname set */ ++ char *uri_path; /* Used only if transport is HTTPS */ + int family; /* May be 0 (aka AF_UNSPEC) if hostname set */ + size_t addrlen; + struct sockaddr_storage addr; +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index 28f1c4d..a4727c4 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -23,6 +23,32 @@ + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ ++/* ++ * MS-KKDCP implementation Copyright 2013,2014 Red Hat, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in ++ * the documentation and/or other materials provided with the ++ * distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS ++ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER ++ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ + + /* Send packet to KDC for realm; wait for response, retransmitting + * as necessary. */ +@@ -49,6 +75,7 @@ + #endif + + #ifdef PROXY_TLS_IMPL_OPENSSL ++#include + #include + #endif + +@@ -116,6 +143,13 @@ struct conn_state { + struct conn_state *next; + time_ms endtime; + krb5_boolean defer; ++ struct { ++ const char *uri_path; ++ char *https_request; ++#ifdef PROXY_TLS_IMPL_OPENSSL ++ SSL *ssl; ++#endif ++ } http; + }; + + void +@@ -140,6 +174,22 @@ get_curtime_ms(time_ms *time_out) + return 0; + } + ++#ifdef PROXY_TLS_IMPL_OPENSSL ++static void ++free_http_ssl_data(struct conn_state *state) ++{ ++ SSL_free(state->http.ssl); ++ state->http.ssl = NULL; ++ free(state->http.https_request); ++ state->http.https_request = NULL; ++} ++#else ++static void ++free_http_ssl_data(struct conn_state *state) ++{ ++} ++#endif ++ + #ifdef USE_POLL + + /* Find a pollfd in selstate by fd, or abort if we can't find it. */ +@@ -321,6 +371,7 @@ socktype_for_transport(k5_transport transport) + case UDP: + return SOCK_DGRAM; + case TCP: ++ case HTTPS: + return SOCK_STREAM; + default: + return 0; +@@ -468,33 +519,113 @@ static fd_handler_fn service_tcp_connect; + static fd_handler_fn service_tcp_write; + static fd_handler_fn service_tcp_read; + static fd_handler_fn service_udp_read; ++static fd_handler_fn service_https_write; ++static fd_handler_fn service_https_read; ++ ++#ifdef PROXY_TLS_IMPL_OPENSSL ++static krb5_error_code ++make_proxy_request(struct conn_state *state, const krb5_data *realm, ++ const krb5_data *message, char **req_out, size_t *len_out) ++{ ++ krb5_kkdcp_message pm; ++ krb5_data *encoded_pm = NULL; ++ struct k5buf buf; ++ const char *uri_path; ++ krb5_error_code ret; ++ ++ *req_out = NULL; ++ *len_out = 0; ++ ++ /* ++ * Stuff the message length in at the front of the kerb_message field ++ * before encoding. The proxied messages are actually the payload we'd ++ * be sending and receiving if we were using plain TCP. ++ */ ++ memset(&pm, 0, sizeof(pm)); ++ ret = alloc_data(&pm.kerb_message, message->length + 4); ++ if (ret != 0) ++ goto cleanup; ++ store_32_be(message->length, pm.kerb_message.data); ++ memcpy(pm.kerb_message.data + 4, message->data, message->length); ++ pm.target_domain = *realm; ++ ret = encode_krb5_kkdcp_message(&pm, &encoded_pm); ++ if (ret != 0) ++ goto cleanup; ++ ++ /* Build the request to transmit: the headers + the proxy message. */ ++ k5_buf_init_dynamic(&buf); ++ uri_path = (state->http.uri_path != NULL) ? state->http.uri_path : ""; ++ k5_buf_add_fmt(&buf, "POST /%s HTTP/1.0\r\n", uri_path); ++ k5_buf_add(&buf, "Cache-Control: no-cache\r\n"); ++ k5_buf_add(&buf, "Pragma: no-cache\r\n"); ++ k5_buf_add(&buf, "User-Agent: kerberos/1.0\r\n"); ++ k5_buf_add(&buf, "Content-type: application/kerberos\r\n"); ++ k5_buf_add_fmt(&buf, "Content-Length: %d\r\n\r\n", encoded_pm->length); ++ k5_buf_add_len(&buf, encoded_pm->data, encoded_pm->length); ++ if (k5_buf_data(&buf) == NULL) { ++ ret = ENOMEM; ++ goto cleanup; ++ } ++ ++ *req_out = k5_buf_data(&buf); ++ *len_out = k5_buf_len(&buf); ++ ++cleanup: ++ krb5_free_data_contents(NULL, &pm.kerb_message); ++ krb5_free_data(NULL, encoded_pm); ++ return ret; ++} ++#else ++static krb5_error_code ++make_proxy_request(struct conn_state *state, const krb5_data *realm, ++ const krb5_data *message, char **req_out, size_t *len_out) ++{ ++ abort(); ++} ++#endif + + /* Set up the actual message we will send across the underlying transport to + * communicate the payload message, using one or both of state->out.sgbuf. */ +-static void +-set_transport_message(struct conn_state *state, const krb5_data *message) ++static krb5_error_code ++set_transport_message(struct conn_state *state, const krb5_data *realm, ++ const krb5_data *message) + { + struct outgoing_message *out = &state->out; ++ char *req = NULL; ++ size_t reqlen; ++ krb5_error_code ret; + + if (message == NULL || message->length == 0) +- return; ++ return 0; + + if (state->addr.transport == TCP) { + store_32_be(message->length, out->msg_len_buf); + SG_SET(&out->sgbuf[0], out->msg_len_buf, 4); + SG_SET(&out->sgbuf[1], message->data, message->length); + out->sg_count = 2; ++ return 0; ++ } else if (state->addr.transport == HTTPS) { ++ ret = make_proxy_request(state, realm, message, &req, &reqlen); ++ if (ret != 0) ++ return ret; ++ SG_SET(&state->out.sgbuf[0], req, reqlen); ++ SG_SET(&state->out.sgbuf[1], 0, 0); ++ state->out.sg_count = 1; ++ free(state->http.https_request); ++ state->http.https_request = req; ++ return 0; + } else { + SG_SET(&out->sgbuf[0], message->data, message->length); + SG_SET(&out->sgbuf[1], NULL, 0); + out->sg_count = 1; ++ return 0; + } + } + + static krb5_error_code + add_connection(struct conn_state **conns, k5_transport transport, + krb5_boolean defer, struct addrinfo *ai, size_t server_index, +- char **udpbufp) ++ const krb5_data *realm, const char *uri_path, char **udpbufp) + { + struct conn_state *state, **tailptr; + +@@ -515,6 +646,11 @@ add_connection(struct conn_state **conns, k5_transport transport, + state->service_connect = service_tcp_connect; + state->service_write = service_tcp_write; + state->service_read = service_tcp_read; ++ } else if (transport == HTTPS) { ++ state->service_connect = service_tcp_connect; ++ state->service_write = service_https_write; ++ state->service_read = service_https_read; ++ state->http.uri_path = uri_path; + } else { + state->service_connect = NULL; + state->service_write = NULL; +@@ -589,10 +725,10 @@ translate_ai_error (int err) + * connections. + */ + static krb5_error_code +-resolve_server(krb5_context context, const struct serverlist *servers, +- size_t ind, k5_transport_strategy strategy, +- const krb5_data *message, char **udpbufp, +- struct conn_state **conns) ++resolve_server(krb5_context context, const krb5_data *realm, ++ const struct serverlist *servers, size_t ind, ++ k5_transport_strategy strategy, const krb5_data *message, ++ char **udpbufp, struct conn_state **conns) + { + krb5_error_code retval; + struct server_entry *entry = &servers->servers[ind]; +@@ -615,7 +751,7 @@ resolve_server(krb5_context context, const struct serverlist *servers, + ai.ai_addr = (struct sockaddr *)&entry->addr; + defer = (entry->transport != transport); + return add_connection(conns, entry->transport, defer, &ai, ind, +- udpbufp); ++ realm, entry->uri_path, udpbufp); + } + + /* If the entry has a specified transport, use it. */ +@@ -639,8 +775,10 @@ resolve_server(krb5_context context, const struct serverlist *servers, + + /* Add each address with the specified or preferred transport. */ + retval = 0; +- for (a = addrs; a != 0 && retval == 0; a = a->ai_next) +- retval = add_connection(conns, transport, FALSE, a, ind, udpbufp); ++ for (a = addrs; a != 0 && retval == 0; a = a->ai_next) { ++ retval = add_connection(conns, transport, FALSE, a, ind, realm, ++ entry->uri_path, udpbufp); ++ } + + /* For TCP_OR_UDP entries, add each address again with the non-preferred + * transport, unless we are avoiding UDP. Flag these as deferred. */ +@@ -648,7 +786,8 @@ resolve_server(krb5_context context, const struct serverlist *servers, + transport = (strategy == UDP_FIRST) ? TCP : UDP; + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) { + a->ai_socktype = socktype_for_transport(transport); +- retval = add_connection(conns, transport, TRUE, a, ind, udpbufp); ++ retval = add_connection(conns, transport, TRUE, a, ind, realm, ++ entry->uri_path, udpbufp); + } + } + freeaddrinfo(addrs); +@@ -658,6 +797,7 @@ resolve_server(krb5_context context, const struct serverlist *servers, + static int + start_connection(krb5_context context, struct conn_state *state, + const krb5_data *message, struct select_state *selstate, ++ const krb5_data *realm, + struct sendto_callback_info *callback_info) + { + int fd, e, type; +@@ -718,7 +858,15 @@ start_connection(krb5_context context, struct conn_state *state, + + message = &state->callback_buffer; + } +- set_transport_message(state, message); ++ ++ e = set_transport_message(state, realm, message); ++ if (e != 0) { ++ TRACE_SENDTO_KDC_ERROR_SET_MESSAGE(context, &state->addr, e); ++ (void) closesocket(state->fd); ++ state->fd = INVALID_SOCKET; ++ state->state = FAILED; ++ return -4; ++ } + + if (state->addr.transport == UDP) { + /* Send it now. */ +@@ -733,7 +881,7 @@ start_connection(krb5_context context, struct conn_state *state, + (void) closesocket(state->fd); + state->fd = INVALID_SOCKET; + state->state = FAILED; +- return -4; ++ return -5; + } else { + state->state = READING; + } +@@ -760,6 +908,7 @@ start_connection(krb5_context context, struct conn_state *state, + static int + maybe_send(krb5_context context, struct conn_state *conn, + const krb5_data *message, struct select_state *selstate, ++ const krb5_data *realm, + struct sendto_callback_info *callback_info) + { + sg_buf *sg; +@@ -767,7 +916,7 @@ maybe_send(krb5_context context, struct conn_state *conn, + + if (conn->state == INITIALIZING) { + return start_connection(context, conn, message, selstate, +- callback_info); ++ realm, callback_info); + } + + /* Did we already shut down this channel? */ +@@ -802,6 +951,8 @@ static void + kill_conn(krb5_context context, struct conn_state *conn, + struct select_state *selstate) + { ++ free_http_ssl_data(conn); ++ + if (socktype_for_transport(conn->addr.transport) == SOCK_STREAM) + TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr); + cm_remove_fd(selstate, conn->fd); +@@ -876,7 +1027,7 @@ service_tcp_connect(krb5_context context, const krb5_data *realm, + if (get_curtime_ms(&conn->endtime) == 0) + conn->endtime += 10000; + +- return service_tcp_write(context, realm, conn, selstate); ++ return conn->service_write(context, realm, conn, selstate); + } + + /* Sets conn->state to READING when done. */ +@@ -982,6 +1133,223 @@ service_udp_read(krb5_context context, const krb5_data *realm, + return TRUE; + } + ++#ifdef PROXY_TLS_IMPL_OPENSSL ++/* Output any error strings that OpenSSL's accumulated as tracing messages. */ ++static void ++flush_ssl_errors(krb5_context context) ++{ ++ unsigned long err; ++ char buf[128]; ++ ++ while ((err = ERR_get_error()) != 0) { ++ ERR_error_string_n(err, buf, sizeof(buf)); ++ TRACE_SENDTO_KDC_HTTPS_ERROR(context, buf); ++ } ++} ++ ++/* ++ * Set up structures that we use to manage the SSL handling for this connection ++ * and apply any non-default settings. Kill the connection and return false if ++ * anything goes wrong while we're doing that; return true otherwise. ++ */ ++static krb5_boolean ++setup_ssl(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) ++{ ++ long options; ++ SSL_CTX *ctx = NULL; ++ SSL *ssl = NULL; ++ ++ /* Do general SSL library setup. */ ++ ctx = SSL_CTX_new(SSLv23_client_method()); ++ if (ctx == NULL) ++ goto kill_conn; ++ options = SSL_CTX_get_options(ctx); ++ SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2); ++ ++ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); ++ if (!SSL_CTX_set_default_verify_paths(ctx)) ++ goto kill_conn; ++ ++ ssl = SSL_new(ctx); ++ if (ssl == NULL) ++ goto kill_conn; ++ ++ /* Tell the SSL library about the socket. */ ++ if (!SSL_set_fd(ssl, conn->fd)) ++ goto kill_conn; ++ SSL_set_connect_state(ssl); ++ ++ SSL_CTX_free(ctx); ++ conn->http.ssl = ssl; ++ ++ return TRUE; ++ ++kill_conn: ++ TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr); ++ flush_ssl_errors(context); ++ SSL_free(ssl); ++ SSL_CTX_free(ctx); ++ kill_conn(context, conn, selstate); ++ return FALSE; ++} ++ ++/* Set conn->state to READING when done; otherwise, call a cm_set_. */ ++static krb5_boolean ++service_https_write(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) ++{ ++ ssize_t nwritten; ++ int e; ++ ++ /* If this is our first time in here, set up the SSL context. */ ++ if (conn->http.ssl == NULL && !setup_ssl(context, realm, conn, selstate)) ++ return FALSE; ++ ++ /* Try to transmit our request to the server. */ ++ nwritten = SSL_write(conn->http.ssl, SG_BUF(conn->out.sgp), ++ SG_LEN(conn->out.sgbuf)); ++ if (nwritten <= 0) { ++ e = SSL_get_error(conn->http.ssl, nwritten); ++ if (e == SSL_ERROR_WANT_READ) { ++ cm_read(selstate, conn->fd); ++ return FALSE; ++ } else if (e == SSL_ERROR_WANT_WRITE) { ++ cm_write(selstate, conn->fd); ++ return FALSE; ++ } ++ TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(context, &conn->addr); ++ flush_ssl_errors(context); ++ kill_conn(context, conn, selstate); ++ return FALSE; ++ } ++ ++ /* Done writing, switch to reading. */ ++ TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr); ++ cm_read(selstate, conn->fd); ++ conn->state = READING; ++ return FALSE; ++} ++ ++/* ++ * Return true on readable data, call a cm_read/write function and return ++ * false if the SSL layer needs it, kill the connection otherwise. ++ */ ++static krb5_boolean ++https_read_bytes(krb5_context context, struct conn_state *conn, ++ struct select_state *selstate) ++{ ++ size_t bufsize; ++ ssize_t nread; ++ krb5_boolean readbytes = FALSE; ++ int e = 0; ++ char *tmp; ++ struct incoming_message *in = &conn->in; ++ ++ for (;;) { ++ if (in->buf == NULL || in->bufsize - in->pos < 1024) { ++ bufsize = in->bufsize ? in->bufsize * 2 : 8192; ++ if (bufsize > 1024 * 1024) { ++ kill_conn(context, conn, selstate); ++ return FALSE; ++ } ++ tmp = realloc(in->buf, bufsize); ++ if (tmp == NULL) { ++ kill_conn(context, conn, selstate); ++ return FALSE; ++ } ++ in->buf = tmp; ++ in->bufsize = bufsize; ++ } ++ ++ nread = SSL_read(conn->http.ssl, &in->buf[in->pos], ++ in->bufsize - in->pos - 1); ++ if (nread <= 0) ++ break; ++ in->pos += nread; ++ in->buf[in->pos] = '\0'; ++ readbytes = TRUE; ++ } ++ ++ e = SSL_get_error(conn->http.ssl, nread); ++ if (e == SSL_ERROR_WANT_READ) { ++ cm_read(selstate, conn->fd); ++ return FALSE; ++ } else if (e == SSL_ERROR_WANT_WRITE) { ++ cm_write(selstate, conn->fd); ++ return FALSE; ++ } else if ((e == SSL_ERROR_ZERO_RETURN) || ++ (e == SSL_ERROR_SYSCALL && nread == 0 && readbytes)) { ++ return TRUE; ++ } ++ ++ e = readbytes ? SOCKET_ERRNO : ECONNRESET; ++ TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr, e); ++ flush_ssl_errors(context); ++ kill_conn(context, conn, selstate); ++ return FALSE; ++} ++ ++/* Return true on readable, valid KKDCPP data. */ ++static krb5_boolean ++service_https_read(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) ++{ ++ krb5_kkdcp_message *pm = NULL; ++ krb5_data buf; ++ const char *rep; ++ struct incoming_message *in = &conn->in; ++ ++ /* Read data through the encryption layer. */ ++ if (!https_read_bytes(context, conn, selstate)) ++ return FALSE; ++ ++ /* Find the beginning of the response body. */ ++ rep = strstr(in->buf, "\r\n\r\n"); ++ if (rep == NULL) ++ goto kill_conn; ++ rep += 4; ++ ++ /* Decode the response. */ ++ buf = make_data((char *)rep, in->pos - (rep - in->buf)); ++ if (decode_krb5_kkdcp_message(&buf, &pm) != 0) ++ goto kill_conn; ++ ++ /* Check and discard the message length at the front of the kerb_message ++ * field after decoding. If it's wrong or missing, something broke. */ ++ if (pm->kerb_message.length < 4 || ++ load_32_be(pm->kerb_message.data) != pm->kerb_message.length - 4) { ++ goto kill_conn; ++ } ++ ++ /* Replace all of the content that we read back with just the message. */ ++ memcpy(in->buf, pm->kerb_message.data + 4, pm->kerb_message.length - 4); ++ in->pos = pm->kerb_message.length - 4; ++ k5_free_kkdcp_message(context, pm); ++ ++ return TRUE; ++ ++kill_conn: ++ TRACE_SENDTO_KDC_HTTPS_ERROR(context, in->buf); ++ k5_free_kkdcp_message(context, pm); ++ kill_conn(context, conn, selstate); ++ return FALSE; ++} ++#else ++static krb5_boolean ++service_https_write(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) ++{ ++ abort(); ++} ++static krb5_boolean ++service_https_read(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) ++{ ++ abort(); ++} ++#endif ++ + /* Return the maximum of endtime and the endtime fields of all currently active + * TCP connections. */ + static time_ms +@@ -1123,7 +1491,7 @@ k5_sendto(krb5_context context, const krb5_data *message, + for (s = 0; s < servers->nservers && !done; s++) { + /* Find the current tail pointer. */ + for (tailptr = &conns; *tailptr != NULL; tailptr = &(*tailptr)->next); +- retval = resolve_server(context, servers, s, strategy, message, ++ retval = resolve_server(context, realm, servers, s, strategy, message, + &udpbuf, &conns); + if (retval) + goto cleanup; +@@ -1132,7 +1500,8 @@ k5_sendto(krb5_context context, const krb5_data *message, + * non-preferred RFC 4120 transport. */ + if (state->defer) + continue; +- if (maybe_send(context, state, message, sel_state, callback_info)) ++ if (maybe_send(context, state, message, sel_state, realm, ++ callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + realm, msg_handler, msg_handler_data, &winner); +@@ -1144,7 +1513,8 @@ k5_sendto(krb5_context context, const krb5_data *message, + for (state = conns; state != NULL && !done; state = state->next) { + if (!state->defer) + continue; +- if (maybe_send(context, state, message, sel_state, callback_info)) ++ if (maybe_send(context, state, message, sel_state, realm, ++ callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + realm, msg_handler, msg_handler_data, &winner); +@@ -1160,7 +1530,8 @@ k5_sendto(krb5_context context, const krb5_data *message, + delay = 4000; + for (pass = 1; pass < MAX_PASS && !done; pass++) { + for (state = conns; state != NULL && !done; state = state->next) { +- if (maybe_send(context, state, message, sel_state, callback_info)) ++ if (maybe_send(context, state, message, sel_state, realm, ++ callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + realm, msg_handler, msg_handler_data, &winner); +@@ -1194,8 +1565,12 @@ k5_sendto(krb5_context context, const krb5_data *message, + cleanup: + for (state = conns; state != NULL; state = next) { + next = state->next; +- if (state->fd != INVALID_SOCKET) ++ if (state->fd != INVALID_SOCKET) { ++ if (socktype_for_transport(state->addr.transport) == SOCK_STREAM) ++ TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &state->addr); + closesocket(state->fd); ++ free_http_ssl_data(state); ++ } + if (state->state == READING && state->in.buf != udpbuf) + free(state->in.buf); + if (callback_info) { +diff --git a/src/lib/krb5/os/t_locate_kdc.c b/src/lib/krb5/os/t_locate_kdc.c +index 300aa71..dd609fd 100644 +--- a/src/lib/krb5/os/t_locate_kdc.c ++++ b/src/lib/krb5/os/t_locate_kdc.c +@@ -39,6 +39,8 @@ ttypename (k5_transport ttype) + return "tcp"; + case UDP: + return "udp"; ++ case HTTPS: ++ return "https"; + default: + snprintf(buf, sizeof(buf), "?%d", ttype); + return buf; +diff --git a/src/lib/krb5/os/trace.c b/src/lib/krb5/os/trace.c +index 8319a86..105a2cd 100644 +--- a/src/lib/krb5/os/trace.c ++++ b/src/lib/krb5/os/trace.c +@@ -201,6 +201,8 @@ trace_format(krb5_context context, const char *fmt, va_list ap) + k5_buf_add(&buf, "dgram"); + else if (ra->transport == TCP) + k5_buf_add(&buf, "stream"); ++ else if (ra->transport == HTTPS) ++ k5_buf_add(&buf, "https"); + else + k5_buf_add_fmt(&buf, "transport%d", ra->transport); + +-- +2.1.0 + diff --git a/0008-Load-custom-anchors-when-using-KKDCP.patch b/0008-Load-custom-anchors-when-using-KKDCP.patch new file mode 100644 index 0000000..19fe964 --- /dev/null +++ b/0008-Load-custom-anchors-when-using-KKDCP.patch @@ -0,0 +1,312 @@ +From f220067c2969aab107bd1300ad1cb8d4855389a7 Mon Sep 17 00:00:00 2001 +From: Nalin Dahyabhai +Date: Thu, 17 Apr 2014 17:17:13 -0400 +Subject: [PATCH 08/13] Load custom anchors when using KKDCP + +Add an http_anchors per-realm setting which we'll apply when using an +HTTPS proxy, more or less mimicking the syntax of its similarly-named +PKINIT counterpart. We only check the [realms] section, though. + +ticket: 7929 +--- + doc/admin/conf_files/krb5_conf.rst | 26 ++++++ + src/include/k5-int.h | 1 + + src/include/k5-trace.h | 7 ++ + src/lib/krb5/os/sendto_kdc.c | 169 ++++++++++++++++++++++++++++++++++++- + 4 files changed, 201 insertions(+), 2 deletions(-) + +diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst +index 19ea9c9..c069327 100644 +--- a/doc/admin/conf_files/krb5_conf.rst ++++ b/doc/admin/conf_files/krb5_conf.rst +@@ -428,6 +428,32 @@ following tags may be specified in the realm's subsection: + (for example, when converting ``rcmd.hostname`` to + ``host/hostname.domain``). + ++**http_anchors** ++ When KDCs and kpasswd servers are accessed through HTTPS proxies, this tag ++ can be used to specify the location of the CA certificate which should be ++ trusted to issue the certificate for a proxy server. If left unspecified, ++ the system-wide default set of CA certificates is used. ++ ++ The syntax for values is similar to that of values for the ++ **pkinit_anchors** tag: ++ ++ **FILE:** *filename* ++ ++ *filename* is assumed to be the name of an OpenSSL-style ca-bundle file. ++ ++ **DIR:** *dirname* ++ ++ *dirname* is assumed to be an directory which contains CA certificates. ++ All files in the directory will be examined; if they contain certificates ++ (in PEM format), they will be used. ++ ++ **ENV:** *envvar* ++ ++ *envvar* specifies the name of an environment variable which has been set ++ to a value conforming to one of the previous values. For example, ++ ``ENV:X509_PROXY_CA``, where environment variable ``X509_PROXY_CA`` has ++ been set to ``FILE:/tmp/my_proxy.pem``. ++ + **kdc** + The name or address of a host running a KDC for that realm. An + optional port number, separated from the hostname by a colon, may +diff --git a/src/include/k5-int.h b/src/include/k5-int.h +index 8f039ee..187d16d 100644 +--- a/src/include/k5-int.h ++++ b/src/include/k5-int.h +@@ -212,6 +212,7 @@ typedef unsigned char u_char; + #define KRB5_CONF_EXTRA_ADDRESSES "extra_addresses" + #define KRB5_CONF_FORWARDABLE "forwardable" + #define KRB5_CONF_HOST_BASED_SERVICES "host_based_services" ++#define KRB5_CONF_HTTP_ANCHORS "http_anchors" + #define KRB5_CONF_IGNORE_ACCEPTOR_HOSTNAME "ignore_acceptor_hostname" + #define KRB5_CONF_IPROP_ENABLE "iprop_enable" + #define KRB5_CONF_IPROP_MASTER_ULOGSIZE "iprop_master_ulogsize" +diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h +index f0d79f1..046bc95 100644 +--- a/src/include/k5-trace.h ++++ b/src/include/k5-trace.h +@@ -324,6 +324,13 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); + TRACE(c, "Resolving hostname {str}", hostname) + #define TRACE_SENDTO_KDC_RESPONSE(c, len, raddr) \ + TRACE(c, "Received answer ({int} bytes) from {raddr}", len, raddr) ++#define TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(c) \ ++ TRACE(c, "HTTPS server certificate not received") ++#define TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(c, depth, \ ++ namelen, name, \ ++ err, errs) \ ++ TRACE(c, "HTTPS certificate error at {int} ({lenstr}): " \ ++ "{int} ({str})", depth, namelen, name, err, errs) + #define TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(c, raddr) \ + TRACE(c, "HTTPS error connecting to {raddr}", raddr) + #define TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(c, raddr, err) \ +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index a4727c4..4bd8698 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -77,6 +77,9 @@ + #ifdef PROXY_TLS_IMPL_OPENSSL + #include + #include ++#include ++#include ++#include + #endif + + #define MAX_PASS 3 +@@ -152,6 +155,11 @@ struct conn_state { + } http; + }; + ++#ifdef PROXY_TLS_IMPL_OPENSSL ++/* Extra-data identifier, used to pass context into the verify callback. */ ++static int ssl_ex_context_id = -1; ++#endif ++ + void + k5_sendto_kdc_initialize(void) + { +@@ -159,6 +167,8 @@ k5_sendto_kdc_initialize(void) + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); ++ ++ ssl_ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + #endif + } + +@@ -1147,6 +1157,152 @@ flush_ssl_errors(krb5_context context) + } + } + ++static krb5_error_code ++load_http_anchor_file(X509_STORE *store, const char *path) ++{ ++ FILE *fp; ++ STACK_OF(X509_INFO) *sk = NULL; ++ X509_INFO *xi; ++ int i; ++ ++ fp = fopen(path, "r"); ++ if (fp == NULL) ++ return errno; ++ sk = PEM_X509_INFO_read(fp, NULL, NULL, NULL); ++ fclose(fp); ++ if (sk == NULL) ++ return ENOENT; ++ for (i = 0; i < sk_X509_INFO_num(sk); i++) { ++ xi = sk_X509_INFO_value(sk, i); ++ if (xi->x509 != NULL) ++ X509_STORE_add_cert(store, xi->x509); ++ } ++ sk_X509_INFO_pop_free(sk, X509_INFO_free); ++ return 0; ++} ++ ++static krb5_error_code ++load_http_anchor_dir(X509_STORE *store, const char *path) ++{ ++ DIR *d = NULL; ++ struct dirent *dentry = NULL; ++ char filename[1024]; ++ krb5_boolean found_any = FALSE; ++ ++ d = opendir(path); ++ if (d == NULL) ++ return ENOENT; ++ while ((dentry = readdir(d)) != NULL) { ++ if (dentry->d_name[0] != '.') { ++ snprintf(filename, sizeof(filename), "%s/%s", ++ path, dentry->d_name); ++ if (load_http_anchor_file(store, filename) == 0) ++ found_any = TRUE; ++ } ++ } ++ closedir(d); ++ return found_any ? 0 : ENOENT; ++} ++ ++static krb5_error_code ++load_http_anchor(SSL_CTX *ctx, const char *location) ++{ ++ X509_STORE *store; ++ const char *envloc; ++ ++ store = SSL_CTX_get_cert_store(ctx); ++ if (strncmp(location, "FILE:", 5) == 0) { ++ return load_http_anchor_file(store, location + 5); ++ } else if (strncmp(location, "DIR:", 4) == 0) { ++ return load_http_anchor_dir(store, location + 4); ++ } else if (strncmp(location, "ENV:", 4) == 0) { ++ envloc = getenv(location + 4); ++ if (envloc == NULL) ++ return ENOENT; ++ return load_http_anchor(ctx, envloc); ++ } ++ return EINVAL; ++} ++ ++static krb5_error_code ++load_http_verify_anchors(krb5_context context, const krb5_data *realm, ++ SSL_CTX *sctx) ++{ ++ const char *anchors[4]; ++ char **values = NULL, *realmz; ++ unsigned int i; ++ krb5_error_code err; ++ ++ realmz = k5memdup0(realm->data, realm->length, &err); ++ if (realmz == NULL) ++ goto cleanup; ++ ++ /* Load the configured anchors. */ ++ anchors[0] = KRB5_CONF_REALMS; ++ anchors[1] = realmz; ++ anchors[2] = KRB5_CONF_HTTP_ANCHORS; ++ anchors[3] = NULL; ++ if (profile_get_values(context->profile, anchors, &values) == 0) { ++ for (i = 0; values[i] != NULL; i++) { ++ err = load_http_anchor(sctx, values[i]); ++ if (err != 0) ++ break; ++ } ++ profile_free_list(values); ++ } else { ++ /* Use the library defaults. */ ++ if (SSL_CTX_set_default_verify_paths(sctx) != 1) ++ err = ENOENT; ++ } ++ ++cleanup: ++ free(realmz); ++ return err; ++} ++ ++static int ++ssl_verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx) ++{ ++ X509 *x; ++ SSL *ssl; ++ BIO *bio; ++ krb5_context context; ++ int err, depth; ++ const char *cert = NULL, *errstr; ++ size_t count; ++ ++ ssl = X509_STORE_CTX_get_ex_data(store_ctx, ++ SSL_get_ex_data_X509_STORE_CTX_idx()); ++ context = SSL_get_ex_data(ssl, ssl_ex_context_id); ++ /* We do have the peer's cert, right? */ ++ x = X509_STORE_CTX_get_current_cert(store_ctx); ++ if (x == NULL) { ++ TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(context); ++ return 0; ++ } ++ /* Figure out where we are. */ ++ depth = X509_STORE_CTX_get_error_depth(store_ctx); ++ if (depth < 0) ++ return 0; ++ /* If there's an error at this level that we're not ignoring, fail. */ ++ err = X509_STORE_CTX_get_error(store_ctx); ++ if (err != X509_V_OK) { ++ bio = BIO_new(BIO_s_mem()); ++ if (bio != NULL) { ++ X509_NAME_print_ex(bio, x->cert_info->subject, 0, 0); ++ count = BIO_get_mem_data(bio, &cert); ++ errstr = X509_verify_cert_error_string(err); ++ TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(context, depth, ++ count, cert, err, ++ errstr); ++ BIO_free(bio); ++ } ++ return 0; ++ } ++ /* All done. */ ++ return 1; ++} ++ + /* + * Set up structures that we use to manage the SSL handling for this connection + * and apply any non-default settings. Kill the connection and return false if +@@ -1156,10 +1312,14 @@ static krb5_boolean + setup_ssl(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) + { ++ int e; + long options; + SSL_CTX *ctx = NULL; + SSL *ssl = NULL; + ++ if (ssl_ex_context_id == -1) ++ goto kill_conn; ++ + /* Do general SSL library setup. */ + ctx = SSL_CTX_new(SSLv23_client_method()); + if (ctx == NULL) +@@ -1167,14 +1327,19 @@ setup_ssl(krb5_context context, const krb5_data *realm, + options = SSL_CTX_get_options(ctx); + SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2); + +- SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); +- if (!SSL_CTX_set_default_verify_paths(ctx)) ++ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, ssl_verify_callback); ++ X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0); ++ e = load_http_verify_anchors(context, realm, ctx); ++ if (e != 0) + goto kill_conn; + + ssl = SSL_new(ctx); + if (ssl == NULL) + goto kill_conn; + ++ if (!SSL_set_ex_data(ssl, ssl_ex_context_id, context)) ++ goto kill_conn; ++ + /* Tell the SSL library about the socket. */ + if (!SSL_set_fd(ssl, conn->fd)) + goto kill_conn; +-- +2.1.0 + diff --git a/0009-Check-names-in-the-server-s-cert-when-using-KKDCP.patch b/0009-Check-names-in-the-server-s-cert-when-using-KKDCP.patch new file mode 100644 index 0000000..14e3354 --- /dev/null +++ b/0009-Check-names-in-the-server-s-cert-when-using-KKDCP.patch @@ -0,0 +1,562 @@ +From f7825e81b1ebf533c1dba9f84ae9ad36073a89cf Mon Sep 17 00:00:00 2001 +From: Nalin Dahyabhai +Date: Thu, 17 Apr 2014 17:19:03 -0400 +Subject: [PATCH 09/13] Check names in the server's cert when using KKDCP + +When we connect to a KDC using an HTTPS proxy, check that the naming +information in the certificate matches the name or address which we +extracted from the server URL in the configuration. + +ticket: 7929 +--- + src/include/k5-trace.h | 5 + + src/lib/krb5/os/Makefile.in | 3 + + src/lib/krb5/os/checkhost.c | 251 +++++++++++++++++++++++++++++++++++++++++++ + src/lib/krb5/os/checkhost.h | 39 +++++++ + src/lib/krb5/os/deps | 14 ++- + src/lib/krb5/os/sendto_kdc.c | 53 +++++++-- + 6 files changed, 355 insertions(+), 10 deletions(-) + create mode 100644 src/lib/krb5/os/checkhost.c + create mode 100644 src/lib/krb5/os/checkhost.h + +diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h +index 046bc95..9e75b29 100644 +--- a/src/include/k5-trace.h ++++ b/src/include/k5-trace.h +@@ -324,6 +324,11 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); + TRACE(c, "Resolving hostname {str}", hostname) + #define TRACE_SENDTO_KDC_RESPONSE(c, len, raddr) \ + TRACE(c, "Received answer ({int} bytes) from {raddr}", len, raddr) ++#define TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MISMATCH(c, hostname) \ ++ TRACE(c, "HTTPS certificate name mismatch: server certificate is " \ ++ "not for \"{str}\"", hostname) ++#define TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MATCH(c, hostname) \ ++ TRACE(c, "HTTPS certificate name matched \"{str}\"", hostname) + #define TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(c) \ + TRACE(c, "HTTPS server certificate not received") + #define TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(c, depth, \ +diff --git a/src/lib/krb5/os/Makefile.in b/src/lib/krb5/os/Makefile.in +index fb4001a..fa8a093 100644 +--- a/src/lib/krb5/os/Makefile.in ++++ b/src/lib/krb5/os/Makefile.in +@@ -13,6 +13,7 @@ STLIBOBJS= \ + c_ustime.o \ + ccdefname.o \ + changepw.o \ ++ checkhost.o \ + dnsglue.o \ + dnssrv.o \ + expand_path.o \ +@@ -59,6 +60,7 @@ OBJS= \ + $(OUTPRE)c_ustime.$(OBJEXT) \ + $(OUTPRE)ccdefname.$(OBJEXT) \ + $(OUTPRE)changepw.$(OBJEXT) \ ++ $(OUTPRE)checkhost.$(OBJEXT) \ + $(OUTPRE)dnsglue.$(OBJEXT) \ + $(OUTPRE)dnssrv.$(OBJEXT) \ + $(OUTPRE)expand_path.$(OBJEXT) \ +@@ -105,6 +107,7 @@ SRCS= \ + $(srcdir)/c_ustime.c \ + $(srcdir)/ccdefname.c \ + $(srcdir)/changepw.c \ ++ $(srcdir)/checkhost.c \ + $(srcdir)/dnsglue.c \ + $(srcdir)/dnssrv.c \ + $(srcdir)/expand_path.c \ +diff --git a/src/lib/krb5/os/checkhost.c b/src/lib/krb5/os/checkhost.c +new file mode 100644 +index 0000000..a91615d +--- /dev/null ++++ b/src/lib/krb5/os/checkhost.c +@@ -0,0 +1,251 @@ ++/* ++ * Copyright 2014 Red Hat, Inc. All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in ++ * the documentation and/or other materials provided with the ++ * distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS ++ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER ++ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "k5-int.h" ++#include "k5-utf8.h" ++ ++#ifdef PROXY_TLS_IMPL_OPENSSL ++#include ++#include ++#include ++#include "checkhost.h" ++ ++/* Return the passed-in character, lower-cased if it's an ASCII character. */ ++static inline char ++ascii_tolower(char p) ++{ ++ if (KRB5_UPPER(p)) ++ return p + ('a' - 'A'); ++ return p; ++} ++ ++/* ++ * Check a single label. If allow_wildcard is true, and the presented name ++ * includes a wildcard, return true and note that we matched a wildcard. ++ * Otherwise, for both the presented and expected values, do a case-insensitive ++ * comparison of ASCII characters, and a case-sensitive comparison of ++ * everything else. ++ */ ++static krb5_boolean ++label_match(const char *presented, size_t plen, const char *expected, ++ size_t elen, krb5_boolean allow_wildcard, krb5_boolean *wildcard) ++{ ++ unsigned int i; ++ ++ if (allow_wildcard && plen == 1 && presented[0] == '*') { ++ *wildcard = TRUE; ++ return TRUE; ++ } ++ ++ if (plen != elen) ++ return FALSE; ++ ++ for (i = 0; i < elen; i++) { ++ if (ascii_tolower(presented[i]) != ascii_tolower(expected[i])) ++ return FALSE; ++ } ++ return TRUE; ++} ++ ++/* Break up the two names and check them, label by label. */ ++static krb5_boolean ++domain_match(const char *presented, size_t plen, const char *expected) ++{ ++ const char *p, *q, *r, *s; ++ int n_label; ++ krb5_boolean used_wildcard = FALSE; ++ ++ n_label = 0; ++ p = presented; ++ r = expected; ++ while (p < presented + plen && *r != '\0') { ++ q = memchr(p, '.', plen - (p - presented)); ++ if (q == NULL) ++ q = presented + plen; ++ s = r + strcspn(r, "."); ++ if (!label_match(p, q - p, r, s - r, n_label == 0, &used_wildcard)) ++ return FALSE; ++ p = q < presented + plen ? q + 1 : q; ++ r = *s ? s + 1 : s; ++ n_label++; ++ } ++ if (used_wildcard && n_label <= 2) ++ return FALSE; ++ if (p == presented + plen && *r == '\0') ++ return TRUE; ++ return FALSE; ++} ++ ++/* Fetch the list of subjectAltNames from a certificate. */ ++static GENERAL_NAMES * ++get_cert_sans(X509 *x) ++{ ++ int ext; ++ X509_EXTENSION *san_ext; ++ ++ ext = X509_get_ext_by_NID(x, NID_subject_alt_name, -1); ++ if (ext < 0) ++ return NULL; ++ san_ext = X509_get_ext(x, ext); ++ if (san_ext == NULL) ++ return NULL; ++ return X509V3_EXT_d2i(san_ext); ++} ++ ++/* Fetch a CN value from the subjct name field, returning its length, or -1 if ++ * there is no subject name or it contains no CN value. */ ++static ssize_t ++get_cert_cn(X509 *x, char *buf, size_t bufsize) ++{ ++ X509_NAME *name; ++ ++ name = X509_get_subject_name(x); ++ if (name == NULL) ++ return -1; ++ return X509_NAME_get_text_by_NID(name, NID_commonName, buf, bufsize); ++} ++ ++/* ++ * Return true if the passed-in expected IP address matches any of the names we ++ * can recover from the server certificate, false otherwise. ++ */ ++krb5_boolean ++k5_check_cert_address(X509 *x, const char *text) ++{ ++ char buf[1024]; ++ GENERAL_NAMES *sans; ++ GENERAL_NAME *san = NULL; ++ ASN1_OCTET_STRING *ip; ++ krb5_boolean found_ip_san = FALSE, matched = FALSE; ++ int n_sans, i; ++ size_t name_length; ++ union { ++ struct in_addr in; ++ struct in6_addr in6; ++ } name; ++ ++ /* Parse the IP address into an octet string. */ ++ ip = M_ASN1_OCTET_STRING_new(); ++ if (ip == NULL) ++ return FALSE; ++ ++ if (inet_aton(text, &name.in) == 1) ++ name_length = sizeof(name.in); ++ else if (inet_pton(AF_INET6, text, &name.in6) == 1) ++ name_length = sizeof(name.in6); ++ else ++ name_length = 0; ++ ++ if (name_length == 0) { ++ ASN1_OCTET_STRING_free(ip); ++ return FALSE; ++ } ++ M_ASN1_OCTET_STRING_set(ip, &name, name_length); ++ ++ /* Check for matches in ipaddress subjectAltName values. */ ++ sans = get_cert_sans(x); ++ if (sans != NULL) { ++ n_sans = sk_GENERAL_NAME_num(sans); ++ for (i = 0; i < n_sans; i++) { ++ san = sk_GENERAL_NAME_value(sans, i); ++ if (san->type != GEN_IPADD) ++ continue; ++ found_ip_san = TRUE; ++ matched = ASN1_OCTET_STRING_cmp(ip, san->d.iPAddress) == 0; ++ if (matched) ++ break; ++ } ++ sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free); ++ } ++ ASN1_OCTET_STRING_free(ip); ++ ++ if (matched) ++ return TRUE; ++ if (found_ip_san) ++ return matched; ++ ++ /* Check for a match against the CN value in the peer's subject name. */ ++ name_length = get_cert_cn(x, buf, sizeof(buf)); ++ if (name_length >= 0) { ++ /* Do a string compare to check if it's an acceptable value. */ ++ return strlen(text) == name_length && ++ strncmp(text, buf, name_length) == 0; ++ } ++ ++ /* We didn't find a match. */ ++ return FALSE; ++} ++ ++/* ++ * Return true if the passed-in expected name matches any of the names we can ++ * recover from a server certificate, false otherwise. ++ */ ++krb5_boolean ++k5_check_cert_servername(X509 *x, const char *expected) ++{ ++ char buf[1024]; ++ GENERAL_NAMES *sans; ++ GENERAL_NAME *san = NULL; ++ unsigned char *dnsname; ++ krb5_boolean found_dns_san = FALSE, matched = FALSE; ++ int name_length, n_sans, i; ++ ++ /* Check for matches in dnsname subjectAltName values. */ ++ sans = get_cert_sans(x); ++ if (sans != NULL) { ++ n_sans = sk_GENERAL_NAME_num(sans); ++ for (i = 0; i < n_sans; i++) { ++ san = sk_GENERAL_NAME_value(sans, i); ++ if (san->type != GEN_DNS) ++ continue; ++ found_dns_san = TRUE; ++ dnsname = NULL; ++ name_length = ASN1_STRING_to_UTF8(&dnsname, san->d.dNSName); ++ if (dnsname == NULL) ++ continue; ++ matched = domain_match((char *)dnsname, name_length, expected); ++ OPENSSL_free(dnsname); ++ if (matched) ++ break; ++ } ++ sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free); ++ } ++ ++ if (matched) ++ return TRUE; ++ if (found_dns_san) ++ return matched; ++ ++ /* Check for a match against the CN value in the peer's subject name. */ ++ name_length = get_cert_cn(x, buf, sizeof(buf)); ++ if (name_length >= 0) ++ return domain_match(buf, name_length, expected); ++ ++ /* We didn't find a match. */ ++ return FALSE; ++} ++#endif +diff --git a/src/lib/krb5/os/checkhost.h b/src/lib/krb5/os/checkhost.h +new file mode 100644 +index 0000000..b9d751e +--- /dev/null ++++ b/src/lib/krb5/os/checkhost.h +@@ -0,0 +1,39 @@ ++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ ++/* ++ * Copyright 2014 Red Hat, Inc. All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in ++ * the documentation and/or other materials provided with the ++ * distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS ++ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER ++ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++/* ++ * Certificate subjectAltName check prototypes. ++ */ ++ ++#ifndef KRB5_LIBOS_CHECKHOST_PROTO__ ++#define KRB5_LIBOS_CHECKHOST_PROTO__ ++ ++krb5_boolean k5_check_cert_servername(X509 *x, const char *expected); ++krb5_boolean k5_check_cert_address(X509 *x, const char *expected); ++ ++#endif /* KRB5_LIBOS_CHECKHOST_PROTO__ */ +diff --git a/src/lib/krb5/os/deps b/src/lib/krb5/os/deps +index 3dd6d46..d56ff30 100644 +--- a/src/lib/krb5/os/deps ++++ b/src/lib/krb5/os/deps +@@ -49,6 +49,17 @@ changepw.so changepw.po $(OUTPRE)changepw.$(OBJEXT): \ + $(top_srcdir)/include/krb5/locate_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + changepw.c os-proto.h ++checkhost.so checkhost.po $(OUTPRE)checkhost.$(OBJEXT): \ ++ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ ++ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ ++ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ ++ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ ++ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ ++ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ ++ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/k5-utf8.h \ ++ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ ++ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ ++ $(top_srcdir)/include/socket-utils.h checkhost.c checkhost.h + dnsglue.so dnsglue.po $(OUTPRE)dnsglue.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ +@@ -418,7 +429,8 @@ sendto_kdc.so sendto_kdc.po $(OUTPRE)sendto_kdc.$(OBJEXT): \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/locate_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ +- $(top_srcdir)/include/socket-utils.h os-proto.h sendto_kdc.c ++ $(top_srcdir)/include/socket-utils.h checkhost.h os-proto.h \ ++ sendto_kdc.c + sn2princ.so sn2princ.po $(OUTPRE)sn2princ.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index 4bd8698..f083c0f 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -80,6 +80,7 @@ + #include + #include + #include ++#include "checkhost.h" + #endif + + #define MAX_PASS 3 +@@ -148,6 +149,7 @@ struct conn_state { + krb5_boolean defer; + struct { + const char *uri_path; ++ const char *servername; + char *https_request; + #ifdef PROXY_TLS_IMPL_OPENSSL + SSL *ssl; +@@ -158,6 +160,7 @@ struct conn_state { + #ifdef PROXY_TLS_IMPL_OPENSSL + /* Extra-data identifier, used to pass context into the verify callback. */ + static int ssl_ex_context_id = -1; ++static int ssl_ex_conn_id = -1; + #endif + + void +@@ -169,6 +172,7 @@ k5_sendto_kdc_initialize(void) + OpenSSL_add_all_algorithms(); + + ssl_ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); ++ ssl_ex_conn_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + #endif + } + +@@ -635,7 +639,8 @@ set_transport_message(struct conn_state *state, const krb5_data *realm, + static krb5_error_code + add_connection(struct conn_state **conns, k5_transport transport, + krb5_boolean defer, struct addrinfo *ai, size_t server_index, +- const krb5_data *realm, const char *uri_path, char **udpbufp) ++ const krb5_data *realm, const char *hostname, ++ const char *uri_path, char **udpbufp) + { + struct conn_state *state, **tailptr; + +@@ -661,6 +666,7 @@ add_connection(struct conn_state **conns, k5_transport transport, + state->service_write = service_https_write; + state->service_read = service_https_read; + state->http.uri_path = uri_path; ++ state->http.servername = hostname; + } else { + state->service_connect = NULL; + state->service_write = NULL; +@@ -760,8 +766,8 @@ resolve_server(krb5_context context, const krb5_data *realm, + ai.ai_addrlen = entry->addrlen; + ai.ai_addr = (struct sockaddr *)&entry->addr; + defer = (entry->transport != transport); +- return add_connection(conns, entry->transport, defer, &ai, ind, +- realm, entry->uri_path, udpbufp); ++ return add_connection(conns, entry->transport, defer, &ai, ind, realm, ++ NULL, entry->uri_path, udpbufp); + } + + /* If the entry has a specified transport, use it. */ +@@ -787,7 +793,7 @@ resolve_server(krb5_context context, const krb5_data *realm, + retval = 0; + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) { + retval = add_connection(conns, transport, FALSE, a, ind, realm, +- entry->uri_path, udpbufp); ++ entry->hostname, entry->uri_path, udpbufp); + } + + /* For TCP_OR_UDP entries, add each address again with the non-preferred +@@ -797,7 +803,7 @@ resolve_server(krb5_context context, const krb5_data *realm, + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) { + a->ai_socktype = socktype_for_transport(transport); + retval = add_connection(conns, transport, TRUE, a, ind, realm, +- entry->uri_path, udpbufp); ++ entry->hostname, entry->uri_path, udpbufp); + } + } + freeaddrinfo(addrs); +@@ -1260,6 +1266,20 @@ cleanup: + return err; + } + ++static krb5_boolean ++ssl_check_name_or_ip(X509 *x, const char *expected_name) ++{ ++ struct in_addr in; ++ struct in6_addr in6; ++ ++ if (inet_aton(expected_name, &in) != 0 || ++ inet_pton(AF_INET6, expected_name, &in6) != 0) { ++ return k5_check_cert_address(x, expected_name); ++ } else { ++ return k5_check_cert_servername(x, expected_name); ++ } ++} ++ + static int + ssl_verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx) + { +@@ -1268,12 +1288,14 @@ ssl_verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx) + BIO *bio; + krb5_context context; + int err, depth; +- const char *cert = NULL, *errstr; ++ struct conn_state *conn = NULL; ++ const char *cert = NULL, *errstr, *expected_name; + size_t count; + + ssl = X509_STORE_CTX_get_ex_data(store_ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + context = SSL_get_ex_data(ssl, ssl_ex_context_id); ++ conn = SSL_get_ex_data(ssl, ssl_ex_conn_id); + /* We do have the peer's cert, right? */ + x = X509_STORE_CTX_get_current_cert(store_ctx); + if (x == NULL) { +@@ -1299,8 +1321,19 @@ ssl_verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx) + } + return 0; + } +- /* All done. */ +- return 1; ++ /* If we're not looking at the peer, we're done and everything's ok. */ ++ if (depth != 0) ++ return 1; ++ /* Check if the name we expect to find is in the certificate. */ ++ expected_name = conn->http.servername; ++ if (ssl_check_name_or_ip(x, expected_name)) { ++ TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MATCH(context, expected_name); ++ return 1; ++ } else { ++ TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MISMATCH(context, expected_name); ++ } ++ /* The name didn't match. */ ++ return 0; + } + + /* +@@ -1317,7 +1350,7 @@ setup_ssl(krb5_context context, const krb5_data *realm, + SSL_CTX *ctx = NULL; + SSL *ssl = NULL; + +- if (ssl_ex_context_id == -1) ++ if (ssl_ex_context_id == -1 || ssl_ex_conn_id == -1) + goto kill_conn; + + /* Do general SSL library setup. */ +@@ -1339,6 +1372,8 @@ setup_ssl(krb5_context context, const krb5_data *realm, + + if (!SSL_set_ex_data(ssl, ssl_ex_context_id, context)) + goto kill_conn; ++ if (!SSL_set_ex_data(ssl, ssl_ex_conn_id, conn)) ++ goto kill_conn; + + /* Tell the SSL library about the socket. */ + if (!SSL_set_fd(ssl, conn->fd)) +-- +2.1.0 + diff --git a/0010-Add-some-longer-form-docs-for-HTTPS.patch b/0010-Add-some-longer-form-docs-for-HTTPS.patch new file mode 100644 index 0000000..88f1327 --- /dev/null +++ b/0010-Add-some-longer-form-docs-for-HTTPS.patch @@ -0,0 +1,86 @@ +From b52acabf478e8d1aa19f7823aade81eed1553143 Mon Sep 17 00:00:00 2001 +From: Nalin Dahyabhai +Date: Tue, 22 Apr 2014 16:31:14 -0400 +Subject: [PATCH 10/13] Add some longer-form docs for HTTPS + +Add some longer-form documentation for the new HTTPS support, walking a +prospective administrator through generating a bare minimal signing +setup, deploying a WSGI-based proxy server onto an Apache httpd server +using mod_ssl and mod_wsgi, and configuring clients to use it. + +ticket: 7929 +--- + doc/admin/https.rst | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + doc/admin/index.rst | 1 + + 2 files changed, 49 insertions(+) + create mode 100644 doc/admin/https.rst + +diff --git a/doc/admin/https.rst b/doc/admin/https.rst +new file mode 100644 +index 0000000..b4e68b2 +--- /dev/null ++++ b/doc/admin/https.rst +@@ -0,0 +1,48 @@ ++.. _https: ++ ++HTTPS proxy configuration ++========================= ++ ++In addition to being able to use UDP or TCP to communicate directly ++with a KDC as is outlined in RFC4120, and with kpasswd services in a ++similar fashion, the client libraries can attempt to use an HTTPS ++proxy server to communicate with a KDC or kpasswd service, using the ++protocol outlined in [MS-KKDCP]. ++ ++Communicating with a KDC through an HTTPS proxy allows clients to ++contact servers when network firewalls might otherwise prevent them ++from doing so. The use of TLS also encrypts all traffic between the ++clients and the KDC, preventing observers from conducting password ++dictionary attacks or from observing the client and server principals ++being authenticated, at additional computational cost to both clients ++and servers. ++ ++An HTTPS proxy server is provided as a feature in some versions of ++Microsoft Windows Server, and a WSGI implementation named `kdcproxy` ++is available in the python package index. ++ ++ ++Configuring the clients ++----------------------- ++ ++To use an HTTPS proxy, a client host must trust the CA which issued ++that proxy's SSL certificate. If that CA's certificate is not in the ++system-wide default set of trusted certificates, configure the ++following relation in the client host's :ref:`krb5.conf(5)` file in ++the appropriate :ref:`realms` subsection:: ++ ++ http_anchors = FILE:/etc/krb5/cacert.pem ++ ++Adjust the pathname to match the path of the file which contains a ++copy of the CA's certificate. The `http_anchors` option is documented ++more fully in :ref:`krb5.conf(5)`. ++ ++Configure the client to access the KDC and kpasswd service by ++specifying their locations in its :ref:`krb5.conf(5)` file in the form ++of HTTPS URLs for the proxy server:: ++ ++ kdc = https://server.fqdn/KdcProxy ++ kpasswd_server = https://server.fqdn/KdcProxy ++ ++If the proxy and client are properly configured, client commands such ++as ``kinit``, ``kvno``, and ``kpasswd`` should all function normally. +diff --git a/doc/admin/index.rst b/doc/admin/index.rst +index 3406843..3cd57f5 100644 +--- a/doc/admin/index.rst ++++ b/doc/admin/index.rst +@@ -17,6 +17,7 @@ For administrators + otp.rst + princ_dns.rst + enctypes.rst ++ https.rst + + .. toctree:: + :maxdepth: 1 +-- +2.1.0 + diff --git a/0011-Have-k5test.py-provide-runenv-to-python-tests.patch b/0011-Have-k5test.py-provide-runenv-to-python-tests.patch new file mode 100644 index 0000000..218e629 --- /dev/null +++ b/0011-Have-k5test.py-provide-runenv-to-python-tests.patch @@ -0,0 +1,64 @@ +From f78f8b1a46534db3a4547323ba952c1fa1b41fe9 Mon Sep 17 00:00:00 2001 +From: Nalin Dahyabhai +Date: Fri, 30 May 2014 17:13:31 -0400 +Subject: [PATCH 11/13] Have k5test.py provide 'runenv' to python tests + +Expose the formerly-internal _runenv module as k5test.runenv, so that +settings we store in the top-level runenv.py will be available to them. + +ticket: 7929 +--- + src/util/k5test.py | 15 ++++++++++----- + 1 file changed, 10 insertions(+), 5 deletions(-) + +diff --git a/src/util/k5test.py b/src/util/k5test.py +index a2bfbee..8cb477d 100644 +--- a/src/util/k5test.py ++++ b/src/util/k5test.py +@@ -177,6 +177,12 @@ Scripts may use the following functions and variables: + + * args: Positional arguments left over after flags are processed. + ++* runenv: The contents of $srctop/runenv.py, containing a dictionary ++ 'env' which specifies additional variables to be added to the realm ++ environment, and a variable 'proxy_tls_impl', which indicates which ++ SSL implementation (if any) is being used by libkrb5's support for ++ contacting KDCs and kpasswd servers over HTTPS. ++ + * verbose: Whether the script is running verbosely. + + * testpass: The command-line test pass argument. The script does not +@@ -526,9 +532,9 @@ def _match_cmdnum(cmdnum, ind): + # Return an environment suitable for running programs in the build + # tree. It is safe to modify the result. + def _build_env(): +- global buildtop, _runenv ++ global buildtop, runenv + env = os.environ.copy() +- for (k, v) in _runenv.iteritems(): ++ for (k, v) in runenv.env.iteritems(): + if v.find('./') == 0: + env[k] = os.path.join(buildtop, v) + else: +@@ -544,8 +550,7 @@ def _import_runenv(): + runenv_py = os.path.join(buildtop, 'runenv.py') + if not os.path.exists(runenv_py): + fail('You must run "make runenv.py" in %s first.' % buildtop) +- module = imp.load_source('runenv', runenv_py) +- return module.env ++ return imp.load_source('runenv', runenv_py) + + + # Merge the nested dictionaries cfg1 and cfg2 into a new dictionary. +@@ -1174,7 +1179,7 @@ _cmd_index = 1 + buildtop = _find_buildtop() + srctop = _find_srctop() + plugins = os.path.join(buildtop, 'plugins') +-_runenv = _import_runenv() ++runenv = _import_runenv() + hostname = _get_hostname() + null_input = open(os.devnull, 'r') + +-- +2.1.0 + diff --git a/0012-Add-a-simple-KDC-proxy-test-server.patch b/0012-Add-a-simple-KDC-proxy-test-server.patch new file mode 100644 index 0000000..48aa032 --- /dev/null +++ b/0012-Add-a-simple-KDC-proxy-test-server.patch @@ -0,0 +1,526 @@ +From 142255ba9af4ce1016a8eadf147e599ee490f1f7 Mon Sep 17 00:00:00 2001 +From: Nalin Dahyabhai +Date: Fri, 7 Feb 2014 18:03:29 -0500 +Subject: [PATCH 12/13] Add a simple KDC proxy test server + +This proxy server uses python-paste to run the kdcproxy from +https://pypi.python.org/pypi/kdcproxy. It should be used along +with the proxy.pem certificate in ../tests/dejagnu/proxy-certs. + +ticket: 7929 +--- + src/tests/dejagnu/proxy-certs/ca.pem | 28 +++++ + src/tests/dejagnu/proxy-certs/make-certs.sh | 124 +++++++++++++++++++++++ + src/tests/dejagnu/proxy-certs/proxy-badsig.pem | 56 ++++++++++ + src/tests/dejagnu/proxy-certs/proxy-ideal.pem | 56 ++++++++++ + src/tests/dejagnu/proxy-certs/proxy-no-match.pem | 54 ++++++++++ + src/tests/dejagnu/proxy-certs/proxy-san.pem | 56 ++++++++++ + src/tests/dejagnu/proxy-certs/proxy-subject.pem | 54 ++++++++++ + src/util/paste-kdcproxy.py | 18 ++++ + 8 files changed, 446 insertions(+) + create mode 100644 src/tests/dejagnu/proxy-certs/ca.pem + create mode 100755 src/tests/dejagnu/proxy-certs/make-certs.sh + create mode 100644 src/tests/dejagnu/proxy-certs/proxy-badsig.pem + create mode 100644 src/tests/dejagnu/proxy-certs/proxy-ideal.pem + create mode 100644 src/tests/dejagnu/proxy-certs/proxy-no-match.pem + create mode 100644 src/tests/dejagnu/proxy-certs/proxy-san.pem + create mode 100644 src/tests/dejagnu/proxy-certs/proxy-subject.pem + create mode 100755 src/util/paste-kdcproxy.py + +diff --git a/src/tests/dejagnu/proxy-certs/ca.pem b/src/tests/dejagnu/proxy-certs/ca.pem +new file mode 100644 +index 0000000..e0f8dc7 +--- /dev/null ++++ b/src/tests/dejagnu/proxy-certs/ca.pem +@@ -0,0 +1,28 @@ ++-----BEGIN CERTIFICATE----- ++MIIEuzCCA6OgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBmTELMAkGA1UEBhMCVVMx ++FjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcTCUNhbWJyaWRnZTEMMAoG ++A1UEChMDTUlUMSIwIAYDVQQLExlJbnNlY3VyZSBLZXJiZXJvcyB0ZXN0IENBMSww ++KgYDVQQDFCN0ZXN0IHN1aXRlIENBOyBkbyBub3QgdXNlIG90aGVyd2lzZTAeFw0x ++NDA1MDIxOTA2MDhaFw0yNTA0MTQxOTA2MDhaMIGZMQswCQYDVQQGEwJVUzEWMBQG ++A1UECBMNTWFzc2FjaHVzZXR0czESMBAGA1UEBxMJQ2FtYnJpZGdlMQwwCgYDVQQK ++EwNNSVQxIjAgBgNVBAsTGUluc2VjdXJlIEtlcmJlcm9zIHRlc3QgQ0ExLDAqBgNV ++BAMUI3Rlc3Qgc3VpdGUgQ0E7IGRvIG5vdCB1c2Ugb3RoZXJ3aXNlMIIBIjANBgkq ++hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1zudnpN8FP7iLn1vgkyTSn/RQxXx1yt6 ++zikHaMrVPjkjXPPUoCFpWS3eeI4aQFoj93L5MwZDmSxOflBAqLwV2AMAacrYnNPJ ++IkHtbYKdVsvw9b4INTWqV9/DOODO7UowyMppmO35/pUXaLL+AjHjLw1/EhQ3ZYtq ++fpAMOkf5TnS5GtqZFlrYgZKE8vTC8BxDKM7FYhWYz7kp/tG3S8O/RTnP7Nd+h1Yd ++pmlHBGfuwIRIJz5xNw6KIcCy3Q0NNoKnh00WVwLmR+x11BGSkMjiZZkwJ5D0RObS ++g13QD/itrGoV2gtPzjQgNPfTrjsMvyOWAAFrWVR3QLTxnnmXsqnXvwIDAQABo4IB ++CjCCAQYwHQYDVR0OBBYEFHO5+DSYzq8rvQhUldyvn0y4AqlHMIHGBgNVHSMEgb4w ++gbuAFHO5+DSYzq8rvQhUldyvn0y4AqlHoYGfpIGcMIGZMQswCQYDVQQGEwJVUzEW ++MBQGA1UECBMNTWFzc2FjaHVzZXR0czESMBAGA1UEBxMJQ2FtYnJpZGdlMQwwCgYD ++VQQKEwNNSVQxIjAgBgNVBAsTGUluc2VjdXJlIEtlcmJlcm9zIHRlc3QgQ0ExLDAq ++BgNVBAMUI3Rlc3Qgc3VpdGUgQ0E7IGRvIG5vdCB1c2Ugb3RoZXJ3aXNlggEBMAsG ++A1UdDwQEAwIB/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAM ++Mf4ptC6WoQBH3GoTfgBL0WlIeYeSFmLO7IaSjpK0FV6F/yF7iPFSXcpmu23m6USY ++LRSxnAvxFTi+h1S5Za9O2Pjq88R9nHmesg4v8HJqOw4HpkDowYo2lumjIMfAutyR ++MQUOujYJW1WyZ2PidN5M1exDeMgQN9nVjUCx/WKD9fnzOjOOR1Sc8Us2KpoyccIi ++A+ABHubCvSO3cln0Sp7qjkssJScZtouzPu8FYiroTIR+1oSIKTpJiik1EptlsTea ++L6fHTMHspFhZaiUJFHWTBAgn/dT+UkFntHdHGI6HWBThFVW05hKoarBA7N25W7FN ++AHyfC0lKds4qFiBQkpdi ++-----END CERTIFICATE----- +diff --git a/src/tests/dejagnu/proxy-certs/make-certs.sh b/src/tests/dejagnu/proxy-certs/make-certs.sh +new file mode 100755 +index 0000000..1191bf0 +--- /dev/null ++++ b/src/tests/dejagnu/proxy-certs/make-certs.sh +@@ -0,0 +1,124 @@ ++#!/bin/sh -e ++ ++PWD=`pwd` ++NAMETYPE=1 ++KEYSIZE=2048 ++DAYS=4000 ++REALM=KRBTEST.COM ++TLS_SERVER_EKU=1.3.6.1.5.5.7.3.1 ++PROXY_EKU_LIST=$TLS_SERVER_EKU ++ ++cat > openssl.cnf << EOF ++[req] ++prompt = no ++distinguished_name = \$ENV::SUBJECT ++ ++[ca] ++default_ca = test_ca ++ ++[test_ca] ++new_certs_dir = $PWD ++serial = $PWD/ca.srl ++database = $PWD/ca.db ++certificate = $PWD/ca.pem ++private_key = $PWD/privkey.pem ++default_days = $DAYS ++x509_extensions = exts_proxy ++policy = proxyname ++default_md = sha1 ++unique_subject = no ++email_in_dn = no ++ ++[signer] ++CN = test CA certificate ++C = US ++ST = Massachusetts ++L = Cambridge ++O = MIT ++OU = Insecure Kerberos test CA ++CN = test suite CA; do not use otherwise ++ ++[proxy] ++C = US ++ST = Massachusetts ++O = KRBTEST.COM ++CN = PROXYinSubject ++ ++[localhost] ++C = US ++ST = Massachusetts ++O = KRBTEST.COM ++CN = localhost ++ ++[proxyname] ++C = supplied ++ST = supplied ++O = supplied ++CN = supplied ++ ++[exts_ca] ++subjectKeyIdentifier = hash ++authorityKeyIdentifier = keyid:always,issuer:always ++keyUsage = nonRepudiation,digitalSignature,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign ++basicConstraints = critical,CA:TRUE ++ ++[exts_proxy] ++subjectKeyIdentifier = hash ++authorityKeyIdentifier = keyid:always,issuer:always ++keyUsage = nonRepudiation,digitalSignature,keyEncipherment,keyAgreement ++basicConstraints = critical,CA:FALSE ++subjectAltName = DNS:proxyŠubjectÄltÑame,DNS:proxySubjectAltName,IP:127.0.0.1,IP:::1,DNS:localhost ++extendedKeyUsage = $PROXY_EKU_LIST ++ ++[exts_proxy_no_san] ++subjectKeyIdentifier = hash ++authorityKeyIdentifier = keyid:always,issuer:always ++keyUsage = nonRepudiation,digitalSignature,keyEncipherment,keyAgreement ++basicConstraints = critical,CA:FALSE ++extendedKeyUsage = $PROXY_EKU_LIST ++EOF ++ ++# Generate a private key. ++openssl genrsa $KEYSIZE -nodes > privkey.pem ++ ++# Generate a "CA" certificate. ++SUBJECT=signer openssl req -config openssl.cnf -new -x509 -extensions exts_ca \ ++ -set_serial 1 -days $DAYS -key privkey.pem -out ca.pem ++ ++# Generate proxy certificate signing requests. ++SUBJECT=proxy openssl req -config openssl.cnf -new -key privkey.pem \ ++ -out proxy.csr ++SUBJECT=localhost openssl req -config openssl.cnf -new -key privkey.pem \ ++ -out localhost.csr ++ ++# Issue the certificate with the right name in a subjectAltName. ++echo 02 > ca.srl ++cat /dev/null > ca.db ++SUBJECT=proxy openssl ca -config openssl.cnf -extensions exts_proxy \ ++ -batch -days $DAYS -notext -out tmp.pem -in proxy.csr ++cat privkey.pem tmp.pem > proxy-san.pem ++ ++# Issue a certificate that only has the name in the subject field ++SUBJECT=proxy openssl ca -config openssl.cnf -extensions exts_proxy_no_san \ ++ -batch -days $DAYS -notext -out tmp.pem -in localhost.csr ++cat privkey.pem tmp.pem > proxy-subject.pem ++ ++# Issue a certificate that doesn't include any matching name values. ++SUBJECT=proxy openssl ca -config openssl.cnf -extensions exts_proxy_no_san \ ++ -batch -days $DAYS -notext -out tmp.pem -in proxy.csr ++cat privkey.pem tmp.pem > proxy-no-match.pem ++ ++# Issue a certificate that contains all matching name values. ++SUBJECT=proxy openssl ca -config openssl.cnf -extensions exts_proxy \ ++ -batch -days $DAYS -notext -out tmp.pem -in localhost.csr ++cat privkey.pem tmp.pem > proxy-ideal.pem ++ ++# Corrupt the signature on the certificate. ++SUBJECT=proxy openssl x509 -outform der -in proxy-ideal.pem -out bad.der ++length=`od -Ad bad.der | tail -n 1 | awk '{print $1}'` ++dd if=/dev/zero bs=1 of=bad.der count=16 seek=`expr $length - 16` ++SUBJECT=proxy openssl x509 -inform der -in bad.der -out tmp.pem ++cat privkey.pem tmp.pem > proxy-badsig.pem ++ ++# Clean up. ++rm -f openssl.cnf proxy.csr localhost.csr privkey.pem ca.db ca.db.old ca.srl ca.srl.old ca.db.attr ca.db.attr.old 02.pem 03.pem 04.pem 05.pem tmp.pem bad.der +diff --git a/src/tests/dejagnu/proxy-certs/proxy-badsig.pem b/src/tests/dejagnu/proxy-certs/proxy-badsig.pem +new file mode 100644 +index 0000000..2b31f7d +--- /dev/null ++++ b/src/tests/dejagnu/proxy-certs/proxy-badsig.pem +@@ -0,0 +1,56 @@ ++-----BEGIN RSA PRIVATE KEY----- ++MIIEpQIBAAKCAQEA1zudnpN8FP7iLn1vgkyTSn/RQxXx1yt6zikHaMrVPjkjXPPU ++oCFpWS3eeI4aQFoj93L5MwZDmSxOflBAqLwV2AMAacrYnNPJIkHtbYKdVsvw9b4I ++NTWqV9/DOODO7UowyMppmO35/pUXaLL+AjHjLw1/EhQ3ZYtqfpAMOkf5TnS5GtqZ ++FlrYgZKE8vTC8BxDKM7FYhWYz7kp/tG3S8O/RTnP7Nd+h1YdpmlHBGfuwIRIJz5x ++Nw6KIcCy3Q0NNoKnh00WVwLmR+x11BGSkMjiZZkwJ5D0RObSg13QD/itrGoV2gtP ++zjQgNPfTrjsMvyOWAAFrWVR3QLTxnnmXsqnXvwIDAQABAoIBAQCqvhpeMDXhGgoo ++Q03wmfrGwPsrMv91aIK1hYrhMPdVs1JAbRYiKh8+pcq07FYa8udRaB4UwkVh/+oM ++/nEs6niRsl/jjQ2l68TFrnNByroynvr6l9Q/EeGecF6Ygo7lY1OsFhcLQM5vjarS ++XhxvdU/6hcRmfS8tGRpUaMWqfmpiN3YgJcgt8SoYhiwAYDTMJjNyWC61lO7IqNVR ++4kntiM24sfAu1sdZynX8Gp2GrpNChapEuhilQ8RayjuStEYr2abcSIjfZFHQXN7o ++TnjL+AQUzc/ZTXDGnIe9ZzZeFz8UCueeoN6KPxfrq9UUWRL6qt7gOIMdhYR6lFxt ++6pj6kLhxAoGBAO5DTnTKDfCMY2/AsTzCJvMGSY0bT1rsdDxrpqjrbUSeMHV3s5Lm ++vEPnnm+05FD/vi99+HZjHXAZFkhA3ubij2qWFPBnQ5YUoh17IW/Ae4bzY2uXikgL ++tLZ+R+OrcGYQQlvPn//PLsxbfdk5vraqzm08kIX0T4o4Iz8ST5NFJ8hVAoGBAOdB ++ahXr14563Cjeu0pSQ1nXoz3IXdnDwePXasYhxQHl8Ayk8qZS5pt7r07H3dqq6pvn ++e09gZINJe47B9UhkR3H5bPyz/kujKS4zqo3Zlbryzm3V0BWqjNj+j8E2YuQKNQr+ ++c480jn2FzwW66w0i3n4U4KUn1w2/iq5AnVzyNkPDAoGAWLYEsyU79XE/4K79DqM3 ++P0r6/afKbw8U5B4syj4FzAOeBU6RNMPmGt5VNkBCtgnSdPpRFTsoDcG5cyN8GrkG ++Lug8WZoJJwr9pT5gH6yqEX/zZ27f1J1PJpd0CsedLNMm8eonJ2arhPkXrVZ7tKV6 ++AGAJa2agatUmAmi96hZYjpUCgYEA32abJEgsedEIhFb/GYI03ELryRCaUXfCA+gj ++lvoihn3qE1z5qGGns4adyX5dPRQmBqxtvDXDg+zl9vg6i0+MkXdCqTD8tXcOnjp9 ++RgFvmyVa9FI8beHPpQTuPNncWK3fpho/6pT8Hhi48LEsxwjrZWOnzQSaxQZH46Q6 ++IQNAFt8CgYEAkflxXvA2/2naix+riaBzv5EVJB7ilbfWiWtq2LEAtwrQ5XNFjrtK ++g45jKrZ/ezAzTfPa5Dwn4xcImd0MIavnJhDu2ATxMGB0GATLlDH2HZvU7UwKLpTW ++6Hlol4yRcX4GSEOxJ2ZpWYNIOYH0yDf1qLJXs1j8Fi3zWRe+V1kff4w= ++-----END RSA PRIVATE KEY----- ++-----BEGIN CERTIFICATE----- ++MIIE3TCCA8WgAwIBAgIBBTANBgkqhkiG9w0BAQUFADCBmTELMAkGA1UEBhMCVVMx ++FjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcTCUNhbWJyaWRnZTEMMAoG ++A1UEChMDTUlUMSIwIAYDVQQLExlJbnNlY3VyZSBLZXJiZXJvcyB0ZXN0IENBMSww ++KgYDVQQDFCN0ZXN0IHN1aXRlIENBOyBkbyBub3QgdXNlIG90aGVyd2lzZTAeFw0x ++NDA1MDIxOTA2MDlaFw0yNTA0MTQxOTA2MDlaME8xCzAJBgNVBAYTAlVTMRYwFAYD ++VQQIEw1NYXNzYWNodXNldHRzMRQwEgYDVQQKEwtLUkJURVNULkNPTTESMBAGA1UE ++AxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1zud ++npN8FP7iLn1vgkyTSn/RQxXx1yt6zikHaMrVPjkjXPPUoCFpWS3eeI4aQFoj93L5 ++MwZDmSxOflBAqLwV2AMAacrYnNPJIkHtbYKdVsvw9b4INTWqV9/DOODO7UowyMpp ++mO35/pUXaLL+AjHjLw1/EhQ3ZYtqfpAMOkf5TnS5GtqZFlrYgZKE8vTC8BxDKM7F ++YhWYz7kp/tG3S8O/RTnP7Nd+h1YdpmlHBGfuwIRIJz5xNw6KIcCy3Q0NNoKnh00W ++VwLmR+x11BGSkMjiZZkwJ5D0RObSg13QD/itrGoV2gtPzjQgNPfTrjsMvyOWAAFr ++WVR3QLTxnnmXsqnXvwIDAQABo4IBdzCCAXMwHQYDVR0OBBYEFHO5+DSYzq8rvQhU ++ldyvn0y4AqlHMIHGBgNVHSMEgb4wgbuAFHO5+DSYzq8rvQhUldyvn0y4AqlHoYGf ++pIGcMIGZMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czESMBAG ++A1UEBxMJQ2FtYnJpZGdlMQwwCgYDVQQKEwNNSVQxIjAgBgNVBAsTGUluc2VjdXJl ++IEtlcmJlcm9zIHRlc3QgQ0ExLDAqBgNVBAMUI3Rlc3Qgc3VpdGUgQ0E7IGRvIG5v ++dCB1c2Ugb3RoZXJ3aXNlggEBMAsGA1UdDwQEAwID6DAMBgNVHRMBAf8EAjAAMFkG ++A1UdEQRSMFCCFnByb3h5xaB1YmplY3TDhGx0w5FhbWWCE3Byb3h5U3ViamVjdEFs ++dE5hbWWHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGCCWxvY2FsaG9zdDATBgNVHSUE ++DDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOCAQEAfTctgFjQSaevBi64q7yh ++GNsK3PqeNEALZz4pSXRbOwm0E4RpYIS7uqg1C4zJ5Zbd4V/dOX7q+T/iBS7gErzS ++rj21jH3Ggc92TmXzcFxMDCxLV0hO8xFkqg3P4sslJESOHxvEMTTf5s893yUb8vJ/ ++DCvZXXRoRwPot9MFozkmcQcaTNunREWFvn4i4JXcMCSAfWTd+/VkpVsy69u3tj68 ++7G2/K5nalvZikutEC+DyfyBuvDAoxIYzCi3VtQxCalW28Q5hzWV21QsvKTP5QBsh ++RaU2r0O58lZPPvrOrtWQBCudUgsnoraVLrjJshEQ4z/ZAAAAAAAAAAAAAAAAAAAA ++AA== ++-----END CERTIFICATE----- +diff --git a/src/tests/dejagnu/proxy-certs/proxy-ideal.pem b/src/tests/dejagnu/proxy-certs/proxy-ideal.pem +new file mode 100644 +index 0000000..4588f7d +--- /dev/null ++++ b/src/tests/dejagnu/proxy-certs/proxy-ideal.pem +@@ -0,0 +1,56 @@ ++-----BEGIN RSA PRIVATE KEY----- ++MIIEpQIBAAKCAQEA1zudnpN8FP7iLn1vgkyTSn/RQxXx1yt6zikHaMrVPjkjXPPU ++oCFpWS3eeI4aQFoj93L5MwZDmSxOflBAqLwV2AMAacrYnNPJIkHtbYKdVsvw9b4I ++NTWqV9/DOODO7UowyMppmO35/pUXaLL+AjHjLw1/EhQ3ZYtqfpAMOkf5TnS5GtqZ ++FlrYgZKE8vTC8BxDKM7FYhWYz7kp/tG3S8O/RTnP7Nd+h1YdpmlHBGfuwIRIJz5x ++Nw6KIcCy3Q0NNoKnh00WVwLmR+x11BGSkMjiZZkwJ5D0RObSg13QD/itrGoV2gtP ++zjQgNPfTrjsMvyOWAAFrWVR3QLTxnnmXsqnXvwIDAQABAoIBAQCqvhpeMDXhGgoo ++Q03wmfrGwPsrMv91aIK1hYrhMPdVs1JAbRYiKh8+pcq07FYa8udRaB4UwkVh/+oM ++/nEs6niRsl/jjQ2l68TFrnNByroynvr6l9Q/EeGecF6Ygo7lY1OsFhcLQM5vjarS ++XhxvdU/6hcRmfS8tGRpUaMWqfmpiN3YgJcgt8SoYhiwAYDTMJjNyWC61lO7IqNVR ++4kntiM24sfAu1sdZynX8Gp2GrpNChapEuhilQ8RayjuStEYr2abcSIjfZFHQXN7o ++TnjL+AQUzc/ZTXDGnIe9ZzZeFz8UCueeoN6KPxfrq9UUWRL6qt7gOIMdhYR6lFxt ++6pj6kLhxAoGBAO5DTnTKDfCMY2/AsTzCJvMGSY0bT1rsdDxrpqjrbUSeMHV3s5Lm ++vEPnnm+05FD/vi99+HZjHXAZFkhA3ubij2qWFPBnQ5YUoh17IW/Ae4bzY2uXikgL ++tLZ+R+OrcGYQQlvPn//PLsxbfdk5vraqzm08kIX0T4o4Iz8ST5NFJ8hVAoGBAOdB ++ahXr14563Cjeu0pSQ1nXoz3IXdnDwePXasYhxQHl8Ayk8qZS5pt7r07H3dqq6pvn ++e09gZINJe47B9UhkR3H5bPyz/kujKS4zqo3Zlbryzm3V0BWqjNj+j8E2YuQKNQr+ ++c480jn2FzwW66w0i3n4U4KUn1w2/iq5AnVzyNkPDAoGAWLYEsyU79XE/4K79DqM3 ++P0r6/afKbw8U5B4syj4FzAOeBU6RNMPmGt5VNkBCtgnSdPpRFTsoDcG5cyN8GrkG ++Lug8WZoJJwr9pT5gH6yqEX/zZ27f1J1PJpd0CsedLNMm8eonJ2arhPkXrVZ7tKV6 ++AGAJa2agatUmAmi96hZYjpUCgYEA32abJEgsedEIhFb/GYI03ELryRCaUXfCA+gj ++lvoihn3qE1z5qGGns4adyX5dPRQmBqxtvDXDg+zl9vg6i0+MkXdCqTD8tXcOnjp9 ++RgFvmyVa9FI8beHPpQTuPNncWK3fpho/6pT8Hhi48LEsxwjrZWOnzQSaxQZH46Q6 ++IQNAFt8CgYEAkflxXvA2/2naix+riaBzv5EVJB7ilbfWiWtq2LEAtwrQ5XNFjrtK ++g45jKrZ/ezAzTfPa5Dwn4xcImd0MIavnJhDu2ATxMGB0GATLlDH2HZvU7UwKLpTW ++6Hlol4yRcX4GSEOxJ2ZpWYNIOYH0yDf1qLJXs1j8Fi3zWRe+V1kff4w= ++-----END RSA PRIVATE KEY----- ++-----BEGIN CERTIFICATE----- ++MIIE3TCCA8WgAwIBAgIBBTANBgkqhkiG9w0BAQUFADCBmTELMAkGA1UEBhMCVVMx ++FjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcTCUNhbWJyaWRnZTEMMAoG ++A1UEChMDTUlUMSIwIAYDVQQLExlJbnNlY3VyZSBLZXJiZXJvcyB0ZXN0IENBMSww ++KgYDVQQDFCN0ZXN0IHN1aXRlIENBOyBkbyBub3QgdXNlIG90aGVyd2lzZTAeFw0x ++NDA1MDIxOTA2MDlaFw0yNTA0MTQxOTA2MDlaME8xCzAJBgNVBAYTAlVTMRYwFAYD ++VQQIEw1NYXNzYWNodXNldHRzMRQwEgYDVQQKEwtLUkJURVNULkNPTTESMBAGA1UE ++AxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1zud ++npN8FP7iLn1vgkyTSn/RQxXx1yt6zikHaMrVPjkjXPPUoCFpWS3eeI4aQFoj93L5 ++MwZDmSxOflBAqLwV2AMAacrYnNPJIkHtbYKdVsvw9b4INTWqV9/DOODO7UowyMpp ++mO35/pUXaLL+AjHjLw1/EhQ3ZYtqfpAMOkf5TnS5GtqZFlrYgZKE8vTC8BxDKM7F ++YhWYz7kp/tG3S8O/RTnP7Nd+h1YdpmlHBGfuwIRIJz5xNw6KIcCy3Q0NNoKnh00W ++VwLmR+x11BGSkMjiZZkwJ5D0RObSg13QD/itrGoV2gtPzjQgNPfTrjsMvyOWAAFr ++WVR3QLTxnnmXsqnXvwIDAQABo4IBdzCCAXMwHQYDVR0OBBYEFHO5+DSYzq8rvQhU ++ldyvn0y4AqlHMIHGBgNVHSMEgb4wgbuAFHO5+DSYzq8rvQhUldyvn0y4AqlHoYGf ++pIGcMIGZMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czESMBAG ++A1UEBxMJQ2FtYnJpZGdlMQwwCgYDVQQKEwNNSVQxIjAgBgNVBAsTGUluc2VjdXJl ++IEtlcmJlcm9zIHRlc3QgQ0ExLDAqBgNVBAMUI3Rlc3Qgc3VpdGUgQ0E7IGRvIG5v ++dCB1c2Ugb3RoZXJ3aXNlggEBMAsGA1UdDwQEAwID6DAMBgNVHRMBAf8EAjAAMFkG ++A1UdEQRSMFCCFnByb3h5xaB1YmplY3TDhGx0w5FhbWWCE3Byb3h5U3ViamVjdEFs ++dE5hbWWHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGCCWxvY2FsaG9zdDATBgNVHSUE ++DDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOCAQEAfTctgFjQSaevBi64q7yh ++GNsK3PqeNEALZz4pSXRbOwm0E4RpYIS7uqg1C4zJ5Zbd4V/dOX7q+T/iBS7gErzS ++rj21jH3Ggc92TmXzcFxMDCxLV0hO8xFkqg3P4sslJESOHxvEMTTf5s893yUb8vJ/ ++DCvZXXRoRwPot9MFozkmcQcaTNunREWFvn4i4JXcMCSAfWTd+/VkpVsy69u3tj68 ++7G2/K5nalvZikutEC+DyfyBuvDAoxIYzCi3VtQxCalW28Q5hzWV21QsvKTP5QBsh ++RaU2r0O58lZPPvrOrtWQBCudUgsnoraVLrjJshEQ4z/ZA9fVtX2ndCSIoyWpWk01 ++gQ== ++-----END CERTIFICATE----- +diff --git a/src/tests/dejagnu/proxy-certs/proxy-no-match.pem b/src/tests/dejagnu/proxy-certs/proxy-no-match.pem +new file mode 100644 +index 0000000..a97c1c7 +--- /dev/null ++++ b/src/tests/dejagnu/proxy-certs/proxy-no-match.pem +@@ -0,0 +1,54 @@ ++-----BEGIN RSA PRIVATE KEY----- ++MIIEpQIBAAKCAQEA1zudnpN8FP7iLn1vgkyTSn/RQxXx1yt6zikHaMrVPjkjXPPU ++oCFpWS3eeI4aQFoj93L5MwZDmSxOflBAqLwV2AMAacrYnNPJIkHtbYKdVsvw9b4I ++NTWqV9/DOODO7UowyMppmO35/pUXaLL+AjHjLw1/EhQ3ZYtqfpAMOkf5TnS5GtqZ ++FlrYgZKE8vTC8BxDKM7FYhWYz7kp/tG3S8O/RTnP7Nd+h1YdpmlHBGfuwIRIJz5x ++Nw6KIcCy3Q0NNoKnh00WVwLmR+x11BGSkMjiZZkwJ5D0RObSg13QD/itrGoV2gtP ++zjQgNPfTrjsMvyOWAAFrWVR3QLTxnnmXsqnXvwIDAQABAoIBAQCqvhpeMDXhGgoo ++Q03wmfrGwPsrMv91aIK1hYrhMPdVs1JAbRYiKh8+pcq07FYa8udRaB4UwkVh/+oM ++/nEs6niRsl/jjQ2l68TFrnNByroynvr6l9Q/EeGecF6Ygo7lY1OsFhcLQM5vjarS ++XhxvdU/6hcRmfS8tGRpUaMWqfmpiN3YgJcgt8SoYhiwAYDTMJjNyWC61lO7IqNVR ++4kntiM24sfAu1sdZynX8Gp2GrpNChapEuhilQ8RayjuStEYr2abcSIjfZFHQXN7o ++TnjL+AQUzc/ZTXDGnIe9ZzZeFz8UCueeoN6KPxfrq9UUWRL6qt7gOIMdhYR6lFxt ++6pj6kLhxAoGBAO5DTnTKDfCMY2/AsTzCJvMGSY0bT1rsdDxrpqjrbUSeMHV3s5Lm ++vEPnnm+05FD/vi99+HZjHXAZFkhA3ubij2qWFPBnQ5YUoh17IW/Ae4bzY2uXikgL ++tLZ+R+OrcGYQQlvPn//PLsxbfdk5vraqzm08kIX0T4o4Iz8ST5NFJ8hVAoGBAOdB ++ahXr14563Cjeu0pSQ1nXoz3IXdnDwePXasYhxQHl8Ayk8qZS5pt7r07H3dqq6pvn ++e09gZINJe47B9UhkR3H5bPyz/kujKS4zqo3Zlbryzm3V0BWqjNj+j8E2YuQKNQr+ ++c480jn2FzwW66w0i3n4U4KUn1w2/iq5AnVzyNkPDAoGAWLYEsyU79XE/4K79DqM3 ++P0r6/afKbw8U5B4syj4FzAOeBU6RNMPmGt5VNkBCtgnSdPpRFTsoDcG5cyN8GrkG ++Lug8WZoJJwr9pT5gH6yqEX/zZ27f1J1PJpd0CsedLNMm8eonJ2arhPkXrVZ7tKV6 ++AGAJa2agatUmAmi96hZYjpUCgYEA32abJEgsedEIhFb/GYI03ELryRCaUXfCA+gj ++lvoihn3qE1z5qGGns4adyX5dPRQmBqxtvDXDg+zl9vg6i0+MkXdCqTD8tXcOnjp9 ++RgFvmyVa9FI8beHPpQTuPNncWK3fpho/6pT8Hhi48LEsxwjrZWOnzQSaxQZH46Q6 ++IQNAFt8CgYEAkflxXvA2/2naix+riaBzv5EVJB7ilbfWiWtq2LEAtwrQ5XNFjrtK ++g45jKrZ/ezAzTfPa5Dwn4xcImd0MIavnJhDu2ATxMGB0GATLlDH2HZvU7UwKLpTW ++6Hlol4yRcX4GSEOxJ2ZpWYNIOYH0yDf1qLJXs1j8Fi3zWRe+V1kff4w= ++-----END RSA PRIVATE KEY----- ++-----BEGIN CERTIFICATE----- ++MIIEhzCCA2+gAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBmTELMAkGA1UEBhMCVVMx ++FjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcTCUNhbWJyaWRnZTEMMAoG ++A1UEChMDTUlUMSIwIAYDVQQLExlJbnNlY3VyZSBLZXJiZXJvcyB0ZXN0IENBMSww ++KgYDVQQDFCN0ZXN0IHN1aXRlIENBOyBkbyBub3QgdXNlIG90aGVyd2lzZTAeFw0x ++NDA1MDIxOTA2MDhaFw0yNTA0MTQxOTA2MDhaMFQxCzAJBgNVBAYTAlVTMRYwFAYD ++VQQIEw1NYXNzYWNodXNldHRzMRQwEgYDVQQKEwtLUkJURVNULkNPTTEXMBUGA1UE ++AxMOUFJPWFlpblN1YmplY3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB ++AQDXO52ek3wU/uIufW+CTJNKf9FDFfHXK3rOKQdoytU+OSNc89SgIWlZLd54jhpA ++WiP3cvkzBkOZLE5+UECovBXYAwBpytic08kiQe1tgp1Wy/D1vgg1NapX38M44M7t ++SjDIymmY7fn+lRdosv4CMeMvDX8SFDdli2p+kAw6R/lOdLka2pkWWtiBkoTy9MLw ++HEMozsViFZjPuSn+0bdLw79FOc/s136HVh2maUcEZ+7AhEgnPnE3DoohwLLdDQ02 ++gqeHTRZXAuZH7HXUEZKQyOJlmTAnkPRE5tKDXdAP+K2sahXaC0/ONCA099OuOwy/ ++I5YAAWtZVHdAtPGeeZeyqde/AgMBAAGjggEcMIIBGDAdBgNVHQ4EFgQUc7n4NJjO ++ryu9CFSV3K+fTLgCqUcwgcYGA1UdIwSBvjCBu4AUc7n4NJjOryu9CFSV3K+fTLgC ++qUehgZ+kgZwwgZkxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRz ++MRIwEAYDVQQHEwlDYW1icmlkZ2UxDDAKBgNVBAoTA01JVDEiMCAGA1UECxMZSW5z ++ZWN1cmUgS2VyYmVyb3MgdGVzdCBDQTEsMCoGA1UEAxQjdGVzdCBzdWl0ZSBDQTsg ++ZG8gbm90IHVzZSBvdGhlcndpc2WCAQEwCwYDVR0PBAQDAgPoMAwGA1UdEwEB/wQC ++MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADggEBAMsP++r4 ++vki0mBJg3POpp0i+H6zNMimoYLLtM5NvwXinfFuFQKbwLm8QWuHVifjfCYxMUm+l ++iL5cS/bq+SUWGDmrlOhsuu4+aYaxgNiEyki5Rol6miSOHbfOhzX8yp0EBPpq08dg ++SEdrTd/FIl4qgkkb1A4RJYZRErn/fbsyjJN66KIfSOXJuC8XMBf03Vw9f2rdrHJa ++r5lVGvqa4wjO2MPq9vVK52VFrbU/zuyyCUtggyIOwGLGSY0Axtbci+IHToDBQes+ ++6W4WwSUCssWfIZXQDLjFw1oRHnN43fXmX5vsVLi7YvOFHOAa1BDnDtCTZit26xVA ++Mdic66hR2jHP0TE= ++-----END CERTIFICATE----- +diff --git a/src/tests/dejagnu/proxy-certs/proxy-san.pem b/src/tests/dejagnu/proxy-certs/proxy-san.pem +new file mode 100644 +index 0000000..ac8bbaa +--- /dev/null ++++ b/src/tests/dejagnu/proxy-certs/proxy-san.pem +@@ -0,0 +1,56 @@ ++-----BEGIN RSA PRIVATE KEY----- ++MIIEpQIBAAKCAQEA1zudnpN8FP7iLn1vgkyTSn/RQxXx1yt6zikHaMrVPjkjXPPU ++oCFpWS3eeI4aQFoj93L5MwZDmSxOflBAqLwV2AMAacrYnNPJIkHtbYKdVsvw9b4I ++NTWqV9/DOODO7UowyMppmO35/pUXaLL+AjHjLw1/EhQ3ZYtqfpAMOkf5TnS5GtqZ ++FlrYgZKE8vTC8BxDKM7FYhWYz7kp/tG3S8O/RTnP7Nd+h1YdpmlHBGfuwIRIJz5x ++Nw6KIcCy3Q0NNoKnh00WVwLmR+x11BGSkMjiZZkwJ5D0RObSg13QD/itrGoV2gtP ++zjQgNPfTrjsMvyOWAAFrWVR3QLTxnnmXsqnXvwIDAQABAoIBAQCqvhpeMDXhGgoo ++Q03wmfrGwPsrMv91aIK1hYrhMPdVs1JAbRYiKh8+pcq07FYa8udRaB4UwkVh/+oM ++/nEs6niRsl/jjQ2l68TFrnNByroynvr6l9Q/EeGecF6Ygo7lY1OsFhcLQM5vjarS ++XhxvdU/6hcRmfS8tGRpUaMWqfmpiN3YgJcgt8SoYhiwAYDTMJjNyWC61lO7IqNVR ++4kntiM24sfAu1sdZynX8Gp2GrpNChapEuhilQ8RayjuStEYr2abcSIjfZFHQXN7o ++TnjL+AQUzc/ZTXDGnIe9ZzZeFz8UCueeoN6KPxfrq9UUWRL6qt7gOIMdhYR6lFxt ++6pj6kLhxAoGBAO5DTnTKDfCMY2/AsTzCJvMGSY0bT1rsdDxrpqjrbUSeMHV3s5Lm ++vEPnnm+05FD/vi99+HZjHXAZFkhA3ubij2qWFPBnQ5YUoh17IW/Ae4bzY2uXikgL ++tLZ+R+OrcGYQQlvPn//PLsxbfdk5vraqzm08kIX0T4o4Iz8ST5NFJ8hVAoGBAOdB ++ahXr14563Cjeu0pSQ1nXoz3IXdnDwePXasYhxQHl8Ayk8qZS5pt7r07H3dqq6pvn ++e09gZINJe47B9UhkR3H5bPyz/kujKS4zqo3Zlbryzm3V0BWqjNj+j8E2YuQKNQr+ ++c480jn2FzwW66w0i3n4U4KUn1w2/iq5AnVzyNkPDAoGAWLYEsyU79XE/4K79DqM3 ++P0r6/afKbw8U5B4syj4FzAOeBU6RNMPmGt5VNkBCtgnSdPpRFTsoDcG5cyN8GrkG ++Lug8WZoJJwr9pT5gH6yqEX/zZ27f1J1PJpd0CsedLNMm8eonJ2arhPkXrVZ7tKV6 ++AGAJa2agatUmAmi96hZYjpUCgYEA32abJEgsedEIhFb/GYI03ELryRCaUXfCA+gj ++lvoihn3qE1z5qGGns4adyX5dPRQmBqxtvDXDg+zl9vg6i0+MkXdCqTD8tXcOnjp9 ++RgFvmyVa9FI8beHPpQTuPNncWK3fpho/6pT8Hhi48LEsxwjrZWOnzQSaxQZH46Q6 ++IQNAFt8CgYEAkflxXvA2/2naix+riaBzv5EVJB7ilbfWiWtq2LEAtwrQ5XNFjrtK ++g45jKrZ/ezAzTfPa5Dwn4xcImd0MIavnJhDu2ATxMGB0GATLlDH2HZvU7UwKLpTW ++6Hlol4yRcX4GSEOxJ2ZpWYNIOYH0yDf1qLJXs1j8Fi3zWRe+V1kff4w= ++-----END RSA PRIVATE KEY----- ++-----BEGIN CERTIFICATE----- ++MIIE4jCCA8qgAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBmTELMAkGA1UEBhMCVVMx ++FjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcTCUNhbWJyaWRnZTEMMAoG ++A1UEChMDTUlUMSIwIAYDVQQLExlJbnNlY3VyZSBLZXJiZXJvcyB0ZXN0IENBMSww ++KgYDVQQDFCN0ZXN0IHN1aXRlIENBOyBkbyBub3QgdXNlIG90aGVyd2lzZTAeFw0x ++NDA1MDIxOTA2MDhaFw0yNTA0MTQxOTA2MDhaMFQxCzAJBgNVBAYTAlVTMRYwFAYD ++VQQIEw1NYXNzYWNodXNldHRzMRQwEgYDVQQKEwtLUkJURVNULkNPTTEXMBUGA1UE ++AxMOUFJPWFlpblN1YmplY3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB ++AQDXO52ek3wU/uIufW+CTJNKf9FDFfHXK3rOKQdoytU+OSNc89SgIWlZLd54jhpA ++WiP3cvkzBkOZLE5+UECovBXYAwBpytic08kiQe1tgp1Wy/D1vgg1NapX38M44M7t ++SjDIymmY7fn+lRdosv4CMeMvDX8SFDdli2p+kAw6R/lOdLka2pkWWtiBkoTy9MLw ++HEMozsViFZjPuSn+0bdLw79FOc/s136HVh2maUcEZ+7AhEgnPnE3DoohwLLdDQ02 ++gqeHTRZXAuZH7HXUEZKQyOJlmTAnkPRE5tKDXdAP+K2sahXaC0/ONCA099OuOwy/ ++I5YAAWtZVHdAtPGeeZeyqde/AgMBAAGjggF3MIIBczAdBgNVHQ4EFgQUc7n4NJjO ++ryu9CFSV3K+fTLgCqUcwgcYGA1UdIwSBvjCBu4AUc7n4NJjOryu9CFSV3K+fTLgC ++qUehgZ+kgZwwgZkxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRz ++MRIwEAYDVQQHEwlDYW1icmlkZ2UxDDAKBgNVBAoTA01JVDEiMCAGA1UECxMZSW5z ++ZWN1cmUgS2VyYmVyb3MgdGVzdCBDQTEsMCoGA1UEAxQjdGVzdCBzdWl0ZSBDQTsg ++ZG8gbm90IHVzZSBvdGhlcndpc2WCAQEwCwYDVR0PBAQDAgPoMAwGA1UdEwEB/wQC ++MAAwWQYDVR0RBFIwUIIWcHJveHnFoHViamVjdMOEbHTDkWFtZYITcHJveHlTdWJq ++ZWN0QWx0TmFtZYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MBMG ++A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBBQUAA4IBAQAH6AWuyRLzMbKq ++MUlyg9ZIar8p0Ms0/UEaa6Xm3/cfm6HSujtgcYlDN3M86Z3zWzWdTrOHsRr/YSG3 ++H3YDhJToKqxcjgho+1xdBPm0xuFsJcypRqGj/mIaJSoa+wC2AdY1EdE+URsh87XC ++SHYNbxAVo8qBHMjtROm6AKb2YusYqHnkT+U6nc4Pn9UnIzmu4wfoSB+X1vtY24TP ++AtXNYQEG4BkgSrcsgoL+z/+wtZLU8QFk6JRO7Bedq711Oh/taEasZHjRAmnqC5TB ++Ab2fnwWuoVZHqz2qydeywXUKrZlctuRVdjE++wOt9xuMPKFGo0PKDw/SymCe61Q8 ++Nc/d2mhz ++-----END CERTIFICATE----- +diff --git a/src/tests/dejagnu/proxy-certs/proxy-subject.pem b/src/tests/dejagnu/proxy-certs/proxy-subject.pem +new file mode 100644 +index 0000000..e17918f +--- /dev/null ++++ b/src/tests/dejagnu/proxy-certs/proxy-subject.pem +@@ -0,0 +1,54 @@ ++-----BEGIN RSA PRIVATE KEY----- ++MIIEpQIBAAKCAQEA1zudnpN8FP7iLn1vgkyTSn/RQxXx1yt6zikHaMrVPjkjXPPU ++oCFpWS3eeI4aQFoj93L5MwZDmSxOflBAqLwV2AMAacrYnNPJIkHtbYKdVsvw9b4I ++NTWqV9/DOODO7UowyMppmO35/pUXaLL+AjHjLw1/EhQ3ZYtqfpAMOkf5TnS5GtqZ ++FlrYgZKE8vTC8BxDKM7FYhWYz7kp/tG3S8O/RTnP7Nd+h1YdpmlHBGfuwIRIJz5x ++Nw6KIcCy3Q0NNoKnh00WVwLmR+x11BGSkMjiZZkwJ5D0RObSg13QD/itrGoV2gtP ++zjQgNPfTrjsMvyOWAAFrWVR3QLTxnnmXsqnXvwIDAQABAoIBAQCqvhpeMDXhGgoo ++Q03wmfrGwPsrMv91aIK1hYrhMPdVs1JAbRYiKh8+pcq07FYa8udRaB4UwkVh/+oM ++/nEs6niRsl/jjQ2l68TFrnNByroynvr6l9Q/EeGecF6Ygo7lY1OsFhcLQM5vjarS ++XhxvdU/6hcRmfS8tGRpUaMWqfmpiN3YgJcgt8SoYhiwAYDTMJjNyWC61lO7IqNVR ++4kntiM24sfAu1sdZynX8Gp2GrpNChapEuhilQ8RayjuStEYr2abcSIjfZFHQXN7o ++TnjL+AQUzc/ZTXDGnIe9ZzZeFz8UCueeoN6KPxfrq9UUWRL6qt7gOIMdhYR6lFxt ++6pj6kLhxAoGBAO5DTnTKDfCMY2/AsTzCJvMGSY0bT1rsdDxrpqjrbUSeMHV3s5Lm ++vEPnnm+05FD/vi99+HZjHXAZFkhA3ubij2qWFPBnQ5YUoh17IW/Ae4bzY2uXikgL ++tLZ+R+OrcGYQQlvPn//PLsxbfdk5vraqzm08kIX0T4o4Iz8ST5NFJ8hVAoGBAOdB ++ahXr14563Cjeu0pSQ1nXoz3IXdnDwePXasYhxQHl8Ayk8qZS5pt7r07H3dqq6pvn ++e09gZINJe47B9UhkR3H5bPyz/kujKS4zqo3Zlbryzm3V0BWqjNj+j8E2YuQKNQr+ ++c480jn2FzwW66w0i3n4U4KUn1w2/iq5AnVzyNkPDAoGAWLYEsyU79XE/4K79DqM3 ++P0r6/afKbw8U5B4syj4FzAOeBU6RNMPmGt5VNkBCtgnSdPpRFTsoDcG5cyN8GrkG ++Lug8WZoJJwr9pT5gH6yqEX/zZ27f1J1PJpd0CsedLNMm8eonJ2arhPkXrVZ7tKV6 ++AGAJa2agatUmAmi96hZYjpUCgYEA32abJEgsedEIhFb/GYI03ELryRCaUXfCA+gj ++lvoihn3qE1z5qGGns4adyX5dPRQmBqxtvDXDg+zl9vg6i0+MkXdCqTD8tXcOnjp9 ++RgFvmyVa9FI8beHPpQTuPNncWK3fpho/6pT8Hhi48LEsxwjrZWOnzQSaxQZH46Q6 ++IQNAFt8CgYEAkflxXvA2/2naix+riaBzv5EVJB7ilbfWiWtq2LEAtwrQ5XNFjrtK ++g45jKrZ/ezAzTfPa5Dwn4xcImd0MIavnJhDu2ATxMGB0GATLlDH2HZvU7UwKLpTW ++6Hlol4yRcX4GSEOxJ2ZpWYNIOYH0yDf1qLJXs1j8Fi3zWRe+V1kff4w= ++-----END RSA PRIVATE KEY----- ++-----BEGIN CERTIFICATE----- ++MIIEgjCCA2qgAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBmTELMAkGA1UEBhMCVVMx ++FjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcTCUNhbWJyaWRnZTEMMAoG ++A1UEChMDTUlUMSIwIAYDVQQLExlJbnNlY3VyZSBLZXJiZXJvcyB0ZXN0IENBMSww ++KgYDVQQDFCN0ZXN0IHN1aXRlIENBOyBkbyBub3QgdXNlIG90aGVyd2lzZTAeFw0x ++NDA1MDIxOTA2MDhaFw0yNTA0MTQxOTA2MDhaME8xCzAJBgNVBAYTAlVTMRYwFAYD ++VQQIEw1NYXNzYWNodXNldHRzMRQwEgYDVQQKEwtLUkJURVNULkNPTTESMBAGA1UE ++AxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1zud ++npN8FP7iLn1vgkyTSn/RQxXx1yt6zikHaMrVPjkjXPPUoCFpWS3eeI4aQFoj93L5 ++MwZDmSxOflBAqLwV2AMAacrYnNPJIkHtbYKdVsvw9b4INTWqV9/DOODO7UowyMpp ++mO35/pUXaLL+AjHjLw1/EhQ3ZYtqfpAMOkf5TnS5GtqZFlrYgZKE8vTC8BxDKM7F ++YhWYz7kp/tG3S8O/RTnP7Nd+h1YdpmlHBGfuwIRIJz5xNw6KIcCy3Q0NNoKnh00W ++VwLmR+x11BGSkMjiZZkwJ5D0RObSg13QD/itrGoV2gtPzjQgNPfTrjsMvyOWAAFr ++WVR3QLTxnnmXsqnXvwIDAQABo4IBHDCCARgwHQYDVR0OBBYEFHO5+DSYzq8rvQhU ++ldyvn0y4AqlHMIHGBgNVHSMEgb4wgbuAFHO5+DSYzq8rvQhUldyvn0y4AqlHoYGf ++pIGcMIGZMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czESMBAG ++A1UEBxMJQ2FtYnJpZGdlMQwwCgYDVQQKEwNNSVQxIjAgBgNVBAsTGUluc2VjdXJl ++IEtlcmJlcm9zIHRlc3QgQ0ExLDAqBgNVBAMUI3Rlc3Qgc3VpdGUgQ0E7IGRvIG5v ++dCB1c2Ugb3RoZXJ3aXNlggEBMAsGA1UdDwQEAwID6DAMBgNVHRMBAf8EAjAAMBMG ++A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBBQUAA4IBAQCzGPT+QOrl9mbJ ++nsGlPlLUOF+PYz0a/9V/iznlofxwCXiRi2ryMpLFbjLeOvjLJ3UzyNKtmEeudTBM ++yfR4i8tb9WA7Oh0BjK1+kD4688bAUXiIDhueKBjonmPvMd9kq3MDd4vDLkcZk6R4 ++4IcbdwhzSBmnJH8ha2J82XShPpRq5CZNR9+vTyFwGdGWdPDjTMiXoXAmpRemcEgO ++iO4Gxvcrg/Z06Ys3eLze7QHNMAEwXhC4rUR34j5I2zgU7CEhff3AktLmnKVa8go8 ++4BJT/n3XGB+3gdAEihQmgCEZetHH+YxAR0Ppn3ty7fpAlOnbRJqpeu6TMN8x/lL8 ++c6JtDWRG ++-----END CERTIFICATE----- +diff --git a/src/util/paste-kdcproxy.py b/src/util/paste-kdcproxy.py +new file mode 100755 +index 0000000..1e56b89 +--- /dev/null ++++ b/src/util/paste-kdcproxy.py +@@ -0,0 +1,18 @@ ++#!/usr/bin/python ++import kdcproxy ++from paste import httpserver ++import os ++import sys ++ ++if len(sys.argv) > 1: ++ port = sys.argv[1] ++else: ++ port = 8443 ++if len(sys.argv) > 2: ++ pem = sys.argv[2] ++else: ++ pem = '*' ++server = httpserver.serve(kdcproxy.Application(), port=port, ssl_pem=pem, ++ start_loop=False) ++os.write(sys.stdout.fileno(), 'proxy server ready\n') ++server.serve_forever() +-- +2.1.0 + diff --git a/0013-Add-tests-for-MS-KKDCP-client-support.patch b/0013-Add-tests-for-MS-KKDCP-client-support.patch new file mode 100644 index 0000000..fdb6eca --- /dev/null +++ b/0013-Add-tests-for-MS-KKDCP-client-support.patch @@ -0,0 +1,259 @@ +Tweaked context for src/tests/Makefile.in because t_salt.py hadn't yet been +added as a test in 1.12, and the rdreq and s2p helpers weren't there yet. + +From 3e2c7cc557048faac3400ae41b0228bd37a82a4c Mon Sep 17 00:00:00 2001 +From: Nalin Dahyabhai +Date: Fri, 7 Feb 2014 18:56:10 -0500 +Subject: [PATCH 13/13] Add tests for MS-KKDCP client support + +Exercise the MS-KKDCP client support using the test proxy server, for +AS, TGS, and kpasswd requests while also checking the certificate +verification and name checks. + +ticket: 7929 +--- + src/tests/Makefile.in | 1 + + src/tests/t_proxy.py | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 220 insertions(+) + create mode 100644 src/tests/t_proxy.py + +diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in +index 7347ed6..536f5cb 100644 +--- a/src/tests/Makefile.in ++++ b/src/tests/Makefile.in +@@ -134,6 +134,7 @@ check-pytests:: t_init_creds t_localauth + $(RUNPYTEST) $(srcdir)/jsonwalker.py -d $(srcdir)/au_dict.json \ + -i au.log + $(RUNPYTEST) $(srcdir)/t_bogus_kdc_req.py $(PYTESTFLAGS) ++ $(RUNPYTEST) $(srcdir)/t_proxy.py $(PYTESTFLAGS) + + clean:: + $(RM) gcred hist hrealm kdbtest plugorder responder +diff --git a/src/tests/t_proxy.py b/src/tests/t_proxy.py +new file mode 100644 +index 0000000..e4e3d48 +--- /dev/null ++++ b/src/tests/t_proxy.py +@@ -0,0 +1,219 @@ ++#!/usr/bin/python ++from k5test import * ++ ++# Skip this test if we're missing proxy functionality or parts of the proxy. ++if runenv.proxy_tls_impl == 'no': ++ success('Warning: not testing proxy support because feature ' + ++ 'was not enabled') ++ exit(0) ++try: ++ from paste import httpserver ++except: ++ success('Warning: not testing proxy support because python ' + ++ 'paste.httpserver module not found') ++ exit(0) ++try: ++ import kdcproxy ++except: ++ success('Warning: not testing proxy support because python ' + ++ 'kdcproxy module not found') ++ exit(0) ++ ++# Construct a krb5.conf fragment configuring the client to use a local proxy ++# server. ++proxysubjectpem = os.path.join(srctop, 'tests', 'dejagnu', 'proxy-certs', ++ 'proxy-subject.pem') ++proxysanpem = os.path.join(srctop, 'tests', 'dejagnu', 'proxy-certs', ++ 'proxy-san.pem') ++proxyidealpem = os.path.join(srctop, 'tests', 'dejagnu', 'proxy-certs', ++ 'proxy-ideal.pem') ++proxywrongpem = os.path.join(srctop, 'tests', 'dejagnu', 'proxy-certs', ++ 'proxy-no-match.pem') ++proxybadpem = os.path.join(srctop, 'tests', 'dejagnu', 'proxy-certs', ++ 'proxy-badsig.pem') ++proxyca = os.path.join(srctop, 'tests', 'dejagnu', 'proxy-certs', 'ca.pem') ++proxyurl = 'https://localhost:$port5/KdcProxy' ++proxyurlupcase = 'https://LocalHost:$port5/KdcProxy' ++proxyurl4 = 'https://127.0.0.1:$port5/KdcProxy' ++proxyurl6 = 'https://[::1]:$port5/KdcProxy' ++ ++unanchored_krb5_conf = {'realms': {'$realm': { ++ 'kdc': proxyurl, ++ 'kpasswd_server': proxyurl}}} ++anchored_name_krb5_conf = {'realms': {'$realm': { ++ 'kdc': proxyurl, ++ 'kpasswd_server': proxyurl, ++ 'http_anchors': 'FILE:%s' % proxyca}}} ++anchored_upcasename_krb5_conf = {'realms': {'$realm': { ++ 'kdc': proxyurlupcase, ++ 'kpasswd_server': proxyurlupcase, ++ 'http_anchors': 'FILE:%s' % proxyca}}} ++anchored_kadmin_krb5_conf = {'realms': {'$realm': { ++ 'kdc': proxyurl, ++ 'admin_server': proxyurl, ++ 'http_anchors': 'FILE:%s' % proxyca}}} ++anchored_ipv4_krb5_conf = {'realms': {'$realm': { ++ 'kdc': proxyurl4, ++ 'kpasswd_server': proxyurl4, ++ 'http_anchors': 'FILE:%s' % proxyca}}} ++kpasswd_input = (password('user') + '\n' + password('user') + '\n' + ++ password('user') + '\n') ++ ++def start_proxy(realm, keycertpem): ++ proxy_conf_path = os.path.join(realm.testdir, 'kdcproxy.conf') ++ proxy_exec_path = os.path.join(srctop, 'util', 'paste-kdcproxy.py') ++ conf = open(proxy_conf_path, 'w') ++ conf.write('[%s]\n' % realm.realm) ++ conf.write('kerberos = kerberos://localhost:%d\n' % realm.portbase) ++ conf.write('kpasswd = kpasswd://localhost:%d\n' % (realm.portbase + 2)) ++ conf.close() ++ realm.env['KDCPROXY_CONFIG'] = proxy_conf_path ++ cmd = [proxy_exec_path, str(realm.server_port()), keycertpem] ++ return realm.start_server(cmd, sentinel='proxy server ready') ++ ++# Fail: untrusted issuer and hostname doesn't match. ++output("running pass 1: issuer not trusted and hostname doesn't match\n") ++realm = K5Realm(krb5_conf=unanchored_krb5_conf, get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxywrongpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Fail: untrusted issuer, host name matches subject. ++output("running pass 2: subject matches, issuer not trusted\n") ++realm = K5Realm(krb5_conf=unanchored_krb5_conf, get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxysubjectpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Fail: untrusted issuer, host name matches subjectAltName. ++output("running pass 3: subjectAltName matches, issuer not trusted\n") ++realm = K5Realm(krb5_conf=unanchored_krb5_conf, get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxysanpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Fail: untrusted issuer, certificate signature is bad. ++output("running pass 4: subject matches, issuer not trusted\n") ++realm = K5Realm(krb5_conf=unanchored_krb5_conf, get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxybadpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Fail: trusted issuer but hostname doesn't match. ++output("running pass 5: issuer trusted but hostname doesn't match\n") ++realm = K5Realm(krb5_conf=anchored_name_krb5_conf, get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxywrongpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Succeed: trusted issuer and host name matches subject. ++output("running pass 6: issuer trusted, subject matches\n") ++realm = K5Realm(krb5_conf=anchored_name_krb5_conf, start_kadmind=True, ++ get_creds=False) ++proxy = start_proxy(realm, proxysubjectpem) ++realm.kinit(realm.user_princ, password=password('user')) ++realm.run([kvno, realm.host_princ]) ++realm.run([kpasswd, realm.user_princ], input=kpasswd_input) ++stop_daemon(proxy) ++realm.stop() ++ ++# Succeed: trusted issuer and host name matches subjectAltName. ++output("running pass 7: issuer trusted, subjectAltName matches\n") ++realm = K5Realm(krb5_conf=anchored_name_krb5_conf, start_kadmind=True, ++ get_creds=False) ++proxy = start_proxy(realm, proxysanpem) ++realm.kinit(realm.user_princ, password=password('user')) ++realm.run([kvno, realm.host_princ]) ++realm.run([kpasswd, realm.user_princ], input=kpasswd_input) ++stop_daemon(proxy) ++realm.stop() ++ ++# Fail: certificate signature is bad. ++output("running pass 8: issuer trusted and subjectAltName matches, sig bad\n") ++realm = K5Realm(krb5_conf=anchored_name_krb5_conf, ++ get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxybadpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Fail: trusted issuer but IP doesn't match. ++output("running pass 9: issuer trusted but no name matches IP\n") ++realm = K5Realm(krb5_conf=anchored_ipv4_krb5_conf, get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxywrongpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Fail: trusted issuer, but subject does not match. ++output("running pass 10: issuer trusted, but subject does not match IP\n") ++realm = K5Realm(krb5_conf=anchored_ipv4_krb5_conf, get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxysubjectpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Succeed: trusted issuer and host name matches subjectAltName. ++output("running pass 11: issuer trusted, subjectAltName matches IP\n") ++realm = K5Realm(krb5_conf=anchored_ipv4_krb5_conf, start_kadmind=True, ++ get_creds=False) ++proxy = start_proxy(realm, proxysanpem) ++realm.kinit(realm.user_princ, password=password('user')) ++realm.run([kvno, realm.host_princ]) ++realm.run([kpasswd, realm.user_princ], input=kpasswd_input) ++stop_daemon(proxy) ++realm.stop() ++ ++# Fail: certificate signature is bad. ++output("running pass 12: issuer trusted, names don't match, signature bad\n") ++realm = K5Realm(krb5_conf=anchored_ipv4_krb5_conf, get_creds=False, ++ create_host=False) ++proxy = start_proxy(realm, proxybadpem) ++realm.kinit(realm.user_princ, password=password('user'), expected_code=1) ++stop_daemon(proxy) ++realm.stop() ++ ++# Succeed: trusted issuer and host name matches subject, using kadmin ++# configuration to find kpasswdd. ++output("running pass 13: issuer trusted, subject matches\n") ++realm = K5Realm(krb5_conf=anchored_kadmin_krb5_conf, start_kadmind=True, ++ get_creds=False, create_host=False) ++proxy = start_proxy(realm, proxysubjectpem) ++realm.run([kpasswd, realm.user_princ], input=kpasswd_input) ++stop_daemon(proxy) ++realm.stop() ++ ++# Succeed: trusted issuer and host name matches subjectAltName, using ++# kadmin configuration to find kpasswdd. ++output("running pass 14: issuer trusted, subjectAltName matches\n") ++realm = K5Realm(krb5_conf=anchored_kadmin_krb5_conf, start_kadmind=True, ++ get_creds=False, create_host=False) ++proxy = start_proxy(realm, proxysanpem) ++realm.run([kpasswd, realm.user_princ], input=kpasswd_input) ++stop_daemon(proxy) ++realm.stop() ++ ++# Succeed: trusted issuer and host name matches subjectAltName (give or take ++# case). ++output("running pass 15: issuer trusted, subjectAltName case-insensitive\n") ++realm = K5Realm(krb5_conf=anchored_upcasename_krb5_conf, start_kadmind=True, ++ get_creds=False, create_host=False) ++proxy = start_proxy(realm, proxysanpem) ++realm.run([kpasswd, realm.user_princ], input=kpasswd_input) ++stop_daemon(proxy) ++realm.stop() ++ ++success('MS-KKDCP proxy') +-- +2.1.0 + diff --git a/krb5-1.10-kpasswd_tcp.patch b/krb5-1.10-kpasswd_tcp.patch deleted file mode 100644 index fd8da8e..0000000 --- a/krb5-1.10-kpasswd_tcp.patch +++ /dev/null @@ -1,32 +0,0 @@ -Fall back to TCP on kdc-unresolvable/unreachable errors. We still have -to wait for UDP to fail, so this might not be ideal. RT #5868. - ---- krb5/src/lib/krb5/os/changepw.c -+++ krb5/src/lib/krb5/os/changepw.c -@@ -270,10 +270,22 @@ change_set_password(krb5_context context - &callback_info, &chpw_rep, ss2sa(&remote_addr), - &addrlen, NULL, NULL, NULL); - if (code) { -- /* -- * Here we may want to switch to TCP on some errors. -- * right? -- */ -+ /* if we're not using a stream socket, and it's an error which -+ * might reasonably be specific to a datagram "connection", try -+ * again with a stream socket */ -+ if (!use_tcp) { -+ switch (code) { -+ case KRB5_KDC_UNREACH: -+ case KRB5_REALM_CANT_RESOLVE: -+ case KRB5KRB_ERR_RESPONSE_TOO_BIG: -+ /* should we do this for more result codes than these? */ -+ k5_free_serverlist (&sl); -+ use_tcp = 1; -+ continue; -+ default: -+ break; -+ } -+ } - break; - } - diff --git a/krb5-1.12-system-exts.patch b/krb5-1.12-system-exts.patch index fc518c4..b90fb1b 100644 --- a/krb5-1.12-system-exts.patch +++ b/krb5-1.12-system-exts.patch @@ -25,8 +25,8 @@ smaller change here. +#define _GNU_SOURCE + #include "fake-addrinfo.h" + #include "k5-tls.h" #include "k5-int.h" - --- krb5-1.11/src/util/support/fake-addrinfo.c +++ krb5-1.11/src/util/support/fake-addrinfo.c @@ -101,6 +101,9 @@ diff --git a/krb5-1.12ish-kpasswd_tcp.patch b/krb5-1.12ish-kpasswd_tcp.patch new file mode 100644 index 0000000..4fdfca4 --- /dev/null +++ b/krb5-1.12ish-kpasswd_tcp.patch @@ -0,0 +1,32 @@ +Fall back to TCP on kdc-unresolvable/unreachable errors. We still have +to wait for UDP to fail, so this might not be ideal. RT #5868. + +--- krb5/src/lib/krb5/os/changepw.c ++++ krb5/src/lib/krb5/os/changepw.c +@@ -270,10 +270,22 @@ change_set_password(krb5_context context + &sl, strategy, &callback_info, &chpw_rep, + ss2sa(&remote_addr), &addrlen, NULL, NULL, NULL); + if (code) { +- /* +- * Here we may want to switch to TCP on some errors. +- * right? +- */ ++ /* if we're not using a stream socket, and it's an error which ++ * might reasonably be specific to a datagram "connection", try ++ * again with a stream socket */ ++ if (!no_udp) { ++ switch (code) { ++ case KRB5_KDC_UNREACH: ++ case KRB5_REALM_CANT_RESOLVE: ++ case KRB5KRB_ERR_RESPONSE_TOO_BIG: ++ /* should we do this for more result codes than these? */ ++ k5_free_serverlist (&sl); ++ no_udp = 1; ++ continue; ++ default: ++ break; ++ } ++ } + break; + } + diff --git a/krb5-1.12ish-tls-plugins.patch b/krb5-1.12ish-tls-plugins.patch new file mode 100644 index 0000000..9004f74 --- /dev/null +++ b/krb5-1.12ish-tls-plugins.patch @@ -0,0 +1,1680 @@ +Adapt to headers being included in a different order in sendto_kdc.c. +Drop portions which drop checkhost.c and checkhost.h. + +commit 472349d2a47fbc7db82e46ba46411b95c312fc1f +Author: Greg Hudson +Date: Sun Jun 22 10:42:14 2014 -0400 + + Move KKDCP OpenSSL code to an internal plugin + + Create an internal pluggable interface "tls" with one in-tree dynamic + plugin module named "k5tls". Move all of the OpenSSL calls to the + plugin module, and make the libkrb5 code load and invoke the plugin. + This way we do not load or initialize libssl unless an HTTP proxy is + used. + + ticket: 7929 + +diff --git a/src/Makefile.in b/src/Makefile.in +index 5e2cf4e..92bb60a 100644 +--- a/src/Makefile.in ++++ b/src/Makefile.in +@@ -20,6 +20,7 @@ SUBDIRS=util include lib \ + @ldap_plugin_dir@ \ + plugins/preauth/otp \ + plugins/preauth/pkinit \ ++ plugins/tls/k5tls \ + kdc kadmin slave clients appl tests \ + config-files build-tools man doc @po@ + WINSUBDIRS=include util lib ccapi windows clients appl +@@ -62,7 +63,7 @@ INSTALLMKDIRS = $(KRB5ROOT) $(KRB5MANROOT) $(KRB5OTHERMKDIRS) \ + $(KRB5_LIBDIR) $(KRB5_INCDIR) \ + $(KRB5_DB_MODULE_DIR) $(KRB5_PA_MODULE_DIR) \ + $(KRB5_AD_MODULE_DIR) \ +- $(KRB5_LIBKRB5_MODULE_DIR) \ ++ $(KRB5_LIBKRB5_MODULE_DIR) $(KRB5_TLS_MODULE_DIR) \ + @localstatedir@ @localstatedir@/krb5kdc \ + $(KRB5_INCSUBDIRS) $(datadir) $(EXAMPLEDIR) \ + $(PKGCONFIG_DIR) +diff --git a/src/config/pre.in b/src/config/pre.in +index e1d7e4b..fd8ee56 100644 +--- a/src/config/pre.in ++++ b/src/config/pre.in +@@ -213,6 +213,7 @@ KRB5_DB_MODULE_DIR = $(MODULE_DIR)/kdb + KRB5_PA_MODULE_DIR = $(MODULE_DIR)/preauth + KRB5_AD_MODULE_DIR = $(MODULE_DIR)/authdata + KRB5_LIBKRB5_MODULE_DIR = $(MODULE_DIR)/libkrb5 ++KRB5_TLS_MODULE_DIR = $(MODULE_DIR)/tls + KRB5_LOCALEDIR = @localedir@ + GSS_MODULE_DIR = @libdir@/gss + KRB5_INCSUBDIRS = \ +diff --git a/src/configure.in b/src/configure.in +index 8aa513e..43509ab 100644 +--- a/src/configure.in ++++ b/src/configure.in +@@ -308,6 +308,11 @@ no) + ;; + esac + ++if test "$PROXY_TLS_IMPL" = no; then ++ AC_DEFINE(PROXY_TLS_IMPL_NONE,1, ++ [Define if no HTTP TLS implementation is selected]) ++fi ++ + AC_SUBST(PROXY_TLS_IMPL) + AC_SUBST(PROXY_TLS_IMPL_CFLAGS) + AC_SUBST(PROXY_TLS_IMPL_LIBS) +@@ -1386,6 +1391,7 @@ dnl ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test + plugins/authdata/greet + plugins/authdata/greet_client + plugins/authdata/greet_server ++ plugins/tls/k5tls + + clients clients/klist clients/kinit clients/kvno + clients/kdestroy clients/kpasswd clients/ksu clients/kswitch +diff --git a/src/include/k5-int.h b/src/include/k5-int.h +index 9f14ee0..38846eb 100644 +--- a/src/include/k5-int.h ++++ b/src/include/k5-int.h +@@ -1083,7 +1083,8 @@ struct plugin_interface { + #define PLUGIN_INTERFACE_LOCALAUTH 5 + #define PLUGIN_INTERFACE_HOSTREALM 6 + #define PLUGIN_INTERFACE_AUDIT 7 +-#define PLUGIN_NUM_INTERFACES 8 ++#define PLUGIN_INTERFACE_TLS 8 ++#define PLUGIN_NUM_INTERFACES 9 + + /* Retrieve the plugin module of type interface_id and name modname, + * storing the result into module. */ +@@ -1126,6 +1127,7 @@ typedef struct krb5_preauth_context_st krb5_preauth_context; + struct ccselect_module_handle; + struct localauth_module_handle; + struct hostrealm_module_handle; ++struct k5_tls_vtable_st; + struct _krb5_context { + krb5_magic magic; + krb5_enctype *in_tkt_etypes; +@@ -1169,6 +1171,9 @@ struct _krb5_context { + /* hostrealm module stuff */ + struct hostrealm_module_handle **hostrealm_handles; + ++ /* TLS module vtable (if loaded) */ ++ struct k5_tls_vtable_st *tls; ++ + /* error detail info */ + struct errinfo err; + +diff --git a/src/include/k5-tls.h b/src/include/k5-tls.h +new file mode 100644 +index 0000000..0661c05 +--- /dev/null ++++ b/src/include/k5-tls.h +@@ -0,0 +1,104 @@ ++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ ++/* include/k5-tls.h - internal pluggable interface for TLS */ ++/* ++ * Copyright (C) 2014 by the Massachusetts Institute of Technology. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in ++ * the documentation and/or other materials provided with the ++ * distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS ++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, ++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, ++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED ++ * OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++/* ++ * This internal pluggable interface allows libkrb5 to load an in-tree module ++ * providing TLS support at runtime. It is currently tailored for the needs of ++ * the OpenSSL module as used for HTTP proxy support. As an internal ++ * interface, it can be changed to fit different implementations and consumers ++ * without regard for backward compatibility. ++ */ ++ ++#ifndef K5_TLS_H ++#define K5_TLS_H ++ ++#include "k5-int.h" ++ ++/* An abstract type for localauth module data. */ ++typedef struct k5_tls_handle_st *k5_tls_handle; ++ ++typedef enum { ++ DATA_READ, DONE, WANT_READ, WANT_WRITE, ERROR_TLS ++} k5_tls_status; ++ ++/* ++ * Create a handle for fd, where the server certificate must match servername ++ * and be trusted according to anchors. anchors is a null-terminated list ++ * using the DIR:/FILE:/ENV: syntax borrowed from PKINIT. If anchors is null, ++ * use the system default trust anchors. ++ */ ++typedef krb5_error_code ++(*k5_tls_setup_fn)(krb5_context context, SOCKET fd, const char *servername, ++ char **anchors, k5_tls_handle *handle_out); ++ ++/* ++ * Write len bytes of data using TLS. Return DONE if writing is complete, ++ * WANT_READ or WANT_WRITE if the underlying socket must be readable or ++ * writable to continue, and ERROR_TLS if the TLS channel or underlying socket ++ * experienced an error. After WANT_READ or WANT_WRITE, the operation will be ++ * retried with the same arguments even if some data has already been written. ++ * (OpenSSL makes this contract easy to fulfill. For other implementations we ++ * might want to change it.) ++ */ ++typedef k5_tls_status ++(*k5_tls_write_fn)(krb5_context context, k5_tls_handle handle, ++ const void *data, size_t len); ++ ++/* ++ * Read up to data_size bytes of data using TLS. Return DATA_READ and set ++ * *len_out if any data is read. Return DONE if there is no more data to be ++ * read on the connection, WANT_READ or WANT_WRITE if the underlying socket ++ * must be readable or writable to continue, and ERROR_TLS if the TLS channel ++ * or underlying socket experienced an error. ++ * ++ * After DATA_READ, there may still be pending buffered data to read. The ++ * caller must call this method again with additional buffer space before ++ * selecting for reading on the underlying socket. ++ */ ++typedef k5_tls_status ++(*k5_tls_read_fn)(krb5_context context, k5_tls_handle handle, void *data, ++ size_t data_size, size_t *len_out); ++ ++/* Release a handle. Do not pass a null pointer. */ ++typedef void ++(*k5_tls_free_handle_fn)(krb5_context context, k5_tls_handle handle); ++ ++/* All functions are mandatory unless they are all null, in which case the ++ * caller should assume that TLS is unsupported. */ ++typedef struct k5_tls_vtable_st { ++ k5_tls_setup_fn setup; ++ k5_tls_write_fn write; ++ k5_tls_read_fn read; ++ k5_tls_free_handle_fn free_handle; ++} *k5_tls_vtable; ++ ++#endif /* K5_TLS_H */ +diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h +index 9e75b29..a0aa85a 100644 +--- a/src/include/k5-trace.h ++++ b/src/include/k5-trace.h +@@ -324,23 +324,11 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); + TRACE(c, "Resolving hostname {str}", hostname) + #define TRACE_SENDTO_KDC_RESPONSE(c, len, raddr) \ + TRACE(c, "Received answer ({int} bytes) from {raddr}", len, raddr) +-#define TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MISMATCH(c, hostname) \ +- TRACE(c, "HTTPS certificate name mismatch: server certificate is " \ +- "not for \"{str}\"", hostname) +-#define TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MATCH(c, hostname) \ +- TRACE(c, "HTTPS certificate name matched \"{str}\"", hostname) +-#define TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(c) \ +- TRACE(c, "HTTPS server certificate not received") +-#define TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(c, depth, \ +- namelen, name, \ +- err, errs) \ +- TRACE(c, "HTTPS certificate error at {int} ({lenstr}): " \ +- "{int} ({str})", depth, namelen, name, err, errs) +-#define TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(c, raddr) \ ++#define TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(c, raddr) \ + TRACE(c, "HTTPS error connecting to {raddr}", raddr) +-#define TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(c, raddr, err) \ +- TRACE(c, "HTTPS error receiving from {raddr}: {errno}", raddr, err) +-#define TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(c, raddr) \ ++#define TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(c, raddr) \ ++ TRACE(c, "HTTPS error receiving from {raddr}", raddr) ++#define TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(c, raddr) \ + TRACE(c, "HTTPS error sending to {raddr}", raddr) + #define TRACE_SENDTO_KDC_HTTPS_SEND(c, raddr) \ + TRACE(c, "Sending HTTPS request to {raddr}", raddr) +@@ -383,6 +371,19 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); + TRACE(c, "TGS reply didn't decode with subkey; trying session key " \ + "({keyblock)}", keyblock) + ++#define TRACE_TLS_ERROR(c, errs) \ ++ TRACE(c, "TLS error: {str}", errs) ++#define TRACE_TLS_NO_REMOTE_CERTIFICATE(c) \ ++ TRACE(c, "TLS server certificate not received") ++#define TRACE_TLS_CERT_ERROR(c, depth, namelen, name, err, errs) \ ++ TRACE(c, "TLS certificate error at {int} ({lenstr}): {int} ({str})", \ ++ depth, namelen, name, err, errs) ++#define TRACE_TLS_SERVER_NAME_MISMATCH(c, hostname) \ ++ TRACE(c, "TLS certificate name mismatch: server certificate is " \ ++ "not for \"{str}\"", hostname) ++#define TRACE_TLS_SERVER_NAME_MATCH(c, hostname) \ ++ TRACE(c, "TLS certificate name matched \"{str}\"", hostname) ++ + #define TRACE_TKT_CREDS(c, creds, cache) \ + TRACE(c, "Getting credentials {creds} using ccache {ccache}", \ + creds, cache) +diff --git a/src/lib/krb5/Makefile.in b/src/lib/krb5/Makefile.in +index 472c008..d9cddc1 100644 +--- a/src/lib/krb5/Makefile.in ++++ b/src/lib/krb5/Makefile.in +@@ -56,8 +56,7 @@ RELDIR=krb5 + SHLIB_EXPDEPS = \ + $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ + $(COM_ERR_DEPLIB) $(SUPPORT_DEPLIB) +-SHLIB_EXPLIBS=-lk5crypto -lcom_err $(PROXY_TLS_IMPL_LIBS) $(SUPPORT_LIB) \ +- @GEN_LIB@ $(LIBS) ++SHLIB_EXPLIBS=-lk5crypto -lcom_err $(SUPPORT_LIB) @GEN_LIB@ $(LIBS) + + all-unix:: all-liblinks + +diff --git a/src/lib/krb5/krb/copy_ctx.c b/src/lib/krb5/krb/copy_ctx.c +index 4237023..322c288 100644 +--- a/src/lib/krb5/krb/copy_ctx.c ++++ b/src/lib/krb5/krb/copy_ctx.c +@@ -81,6 +81,7 @@ krb5_copy_context(krb5_context ctx, krb5_context *nctx_out) + nctx->ccselect_handles = NULL; + nctx->localauth_handles = NULL; + nctx->hostrealm_handles = NULL; ++ nctx->tls = NULL; + nctx->kdblog_context = NULL; + nctx->trace_callback = NULL; + nctx->trace_callback_data = NULL; +diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c +index 6801bb1..6548f36 100644 +--- a/src/lib/krb5/krb/init_ctx.c ++++ b/src/lib/krb5/krb/init_ctx.c +@@ -319,6 +319,7 @@ krb5_free_context(krb5_context ctx) + k5_localauth_free_context(ctx); + k5_plugin_free_context(ctx); + free(ctx->plugin_base_dir); ++ free(ctx->tls); + + ctx->magic = 0; + free(ctx); +diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c +index 8b62c7b..7375f51 100644 +--- a/src/lib/krb5/krb/plugin.c ++++ b/src/lib/krb5/krb/plugin.c +@@ -55,7 +55,8 @@ const char *interface_names[] = { + "ccselect", + "localauth", + "hostrealm", +- "audit" ++ "audit", ++ "tls" + }; + + /* Return the context's interface structure for id, or NULL if invalid. */ +diff --git a/src/lib/krb5/krb5_libinit.c b/src/lib/krb5/krb5_libinit.c +index b72bc58..eb40124 100644 +--- a/src/lib/krb5/krb5_libinit.c ++++ b/src/lib/krb5/krb5_libinit.c +@@ -55,8 +55,6 @@ int krb5int_lib_init(void) + if (err) + return err; + +- k5_sendto_kdc_initialize(); +- + return 0; + } + +diff --git a/src/lib/krb5/os/Makefile.in b/src/lib/krb5/os/Makefile.in +index fa8a093..ea68990 100644 +--- a/src/lib/krb5/os/Makefile.in ++++ b/src/lib/krb5/os/Makefile.in +@@ -2,7 +2,7 @@ mydir=lib$(S)krb5$(S)os + BUILDTOP=$(REL)..$(S)..$(S).. + DEFINES=-DLIBDIR=\"$(KRB5_LIBDIR)\" -DBINDIR=\"$(CLIENT_BINDIR)\" \ + -DSBINDIR=\"$(ADMIN_BINDIR)\" +-LOCALINCLUDES= $(PROXY_TLS_IMPL_CFLAGS) -I$(top_srcdir)/util/profile ++LOCALINCLUDES= -I$(top_srcdir)/util/profile + + ##DOS##BUILDTOP = ..\..\.. + ##DOS##PREFIXDIR=os +@@ -13,7 +13,6 @@ STLIBOBJS= \ + c_ustime.o \ + ccdefname.o \ + changepw.o \ +- checkhost.o \ + dnsglue.o \ + dnssrv.o \ + expand_path.o \ +diff --git a/src/lib/krb5/os/deps b/src/lib/krb5/os/deps +index d56ff30..211354c 100644 +--- a/src/lib/krb5/os/deps ++++ b/src/lib/krb5/os/deps +@@ -49,17 +49,6 @@ changepw.so changepw.po $(OUTPRE)changepw.$(OBJEXT): \ + $(top_srcdir)/include/krb5/locate_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + changepw.c os-proto.h +-checkhost.so checkhost.po $(OUTPRE)checkhost.$(OBJEXT): \ +- $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ +- $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ +- $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ +- $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ +- $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ +- $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ +- $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/k5-utf8.h \ +- $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ +- $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ +- $(top_srcdir)/include/socket-utils.h checkhost.c checkhost.h + dnsglue.so dnsglue.po $(OUTPRE)dnsglue.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ +@@ -429,7 +418,7 @@ sendto_kdc.so sendto_kdc.po $(OUTPRE)sendto_kdc.$(OBJEXT): \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/locate_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ +- $(top_srcdir)/include/socket-utils.h checkhost.h os-proto.h \ ++ $(top_srcdir)/include/socket-utils.h os-proto.h \ + sendto_kdc.c + sn2princ.so sn2princ.po $(OUTPRE)sn2princ.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ +diff --git a/src/lib/krb5/os/locate_kdc.c b/src/lib/krb5/os/locate_kdc.c +index 1f2039c..160a2d0 100644 +--- a/src/lib/krb5/os/locate_kdc.c ++++ b/src/lib/krb5/os/locate_kdc.c +@@ -177,7 +177,6 @@ oom: + return ENOMEM; + } + +-#ifdef PROXY_TLS_IMPL_OPENSSL + static void + parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host, + char **uri_path) +@@ -195,13 +194,6 @@ parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host, + } + } + } +-#else +-static void +-parse_uri_if_https(char *host_or_uri, k5_transport *transport, char **host, +- char **uri) +-{ +-} +-#endif + + /* Return true if server is identical to an entry in list. */ + static krb5_boolean +diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h +index 34bf028..69ee376 100644 +--- a/src/lib/krb5/os/os-proto.h ++++ b/src/lib/krb5/os/os-proto.h +@@ -187,6 +187,5 @@ krb5_error_code localauth_k5login_initvt(krb5_context context, int maj_ver, + krb5_plugin_vtable vtable); + krb5_error_code localauth_an2ln_initvt(krb5_context context, int maj_ver, + int min_ver, krb5_plugin_vtable vtable); +-void k5_sendto_kdc_initialize(void); + + #endif /* KRB5_LIBOS_INT_PROTO__ */ +diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c +index a572831..2242240 100644 +--- a/src/lib/krb5/os/sendto_kdc.c ++++ b/src/lib/krb5/os/sendto_kdc.c +@@ -54,6 +54,7 @@ + * as necessary. */ + + #include "fake-addrinfo.h" ++#include "k5-tls.h" + #include "k5-int.h" + + #include "os-proto.h" +@@ -74,15 +75,6 @@ + #endif + #endif + +-#ifdef PROXY_TLS_IMPL_OPENSSL +-#include +-#include +-#include +-#include +-#include +-#include "checkhost.h" +-#endif +- + #define MAX_PASS 3 + #define DEFAULT_UDP_PREF_LIMIT 1465 + #define HARD_UDP_LIMIT 32700 /* could probably do 64K-epsilon ? */ +@@ -147,29 +139,30 @@ struct conn_state { + const char *uri_path; + const char *servername; + char *https_request; +-#ifdef PROXY_TLS_IMPL_OPENSSL +- SSL *ssl; +-#endif ++ k5_tls_handle tls; + } http; + }; + +-#ifdef PROXY_TLS_IMPL_OPENSSL +-/* Extra-data identifier, used to pass context into the verify callback. */ +-static int ssl_ex_context_id = -1; +-static int ssl_ex_conn_id = -1; +-#endif +- +-void +-k5_sendto_kdc_initialize(void) ++/* Set up context->tls. On allocation failure, return ENOMEM. On plugin load ++ * failure, set context->tls to point to a nulled vtable and return 0. */ ++static krb5_error_code ++init_tls_vtable(krb5_context context) + { +-#ifdef PROXY_TLS_IMPL_OPENSSL +- SSL_library_init(); +- SSL_load_error_strings(); +- OpenSSL_add_all_algorithms(); ++ krb5_plugin_initvt_fn initfn; + +- ssl_ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); +- ssl_ex_conn_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); +-#endif ++ if (context->tls != NULL) ++ return 0; ++ ++ context->tls = calloc(1, sizeof(*context->tls)); ++ if (context->tls == NULL) ++ return ENOMEM; ++ ++ /* Attempt to load the module; just let it stay nulled out on failure. */ ++ k5_plugin_register_dyn(context, PLUGIN_INTERFACE_TLS, "k5tls", "tls"); ++ if (k5_plugin_load(context, PLUGIN_INTERFACE_TLS, "k5tls", &initfn) == 0) ++ (*initfn)(context, 0, 0, (krb5_plugin_vtable)context->tls); ++ ++ return 0; + } + + /* Get current time in milliseconds. */ +@@ -184,21 +177,15 @@ get_curtime_ms(time_ms *time_out) + return 0; + } + +-#ifdef PROXY_TLS_IMPL_OPENSSL + static void +-free_http_ssl_data(struct conn_state *state) ++free_http_tls_data(krb5_context context, struct conn_state *state) + { +- SSL_free(state->http.ssl); +- state->http.ssl = NULL; ++ if (state->http.tls != NULL) ++ context->tls->free_handle(context, state->http.tls); ++ state->http.tls = NULL; + free(state->http.https_request); + state->http.https_request = NULL; + } +-#else +-static void +-free_http_ssl_data(struct conn_state *state) +-{ +-} +-#endif + + #ifdef USE_POLL + +@@ -532,7 +519,6 @@ static fd_handler_fn service_udp_read; + static fd_handler_fn service_https_write; + static fd_handler_fn service_https_read; + +-#ifdef PROXY_TLS_IMPL_OPENSSL + static krb5_error_code + make_proxy_request(struct conn_state *state, const krb5_data *realm, + const krb5_data *message, char **req_out, size_t *len_out) +@@ -585,14 +571,6 @@ cleanup: + krb5_free_data(NULL, encoded_pm); + return ret; + } +-#else +-static krb5_error_code +-make_proxy_request(struct conn_state *state, const krb5_data *realm, +- const krb5_data *message, char **req_out, size_t *len_out) +-{ +- abort(); +-} +-#endif + + /* Set up the actual message we will send across the underlying transport to + * communicate the payload message, using one or both of state->out.sgbuf. */ +@@ -963,7 +941,7 @@ static void + kill_conn(krb5_context context, struct conn_state *conn, + struct select_state *selstate) + { +- free_http_ssl_data(conn); ++ free_http_tls_data(context, conn); + + if (socktype_for_transport(conn->addr.transport) == SOCK_STREAM) + TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr); +@@ -1145,249 +1123,44 @@ service_udp_read(krb5_context context, const krb5_data *realm, + return TRUE; + } + +-#ifdef PROXY_TLS_IMPL_OPENSSL +-/* Output any error strings that OpenSSL's accumulated as tracing messages. */ +-static void +-flush_ssl_errors(krb5_context context) +-{ +- unsigned long err; +- char buf[128]; +- +- while ((err = ERR_get_error()) != 0) { +- ERR_error_string_n(err, buf, sizeof(buf)); +- TRACE_SENDTO_KDC_HTTPS_ERROR(context, buf); +- } +-} +- +-static krb5_error_code +-load_http_anchor_file(X509_STORE *store, const char *path) +-{ +- FILE *fp; +- STACK_OF(X509_INFO) *sk = NULL; +- X509_INFO *xi; +- int i; +- +- fp = fopen(path, "r"); +- if (fp == NULL) +- return errno; +- sk = PEM_X509_INFO_read(fp, NULL, NULL, NULL); +- fclose(fp); +- if (sk == NULL) +- return ENOENT; +- for (i = 0; i < sk_X509_INFO_num(sk); i++) { +- xi = sk_X509_INFO_value(sk, i); +- if (xi->x509 != NULL) +- X509_STORE_add_cert(store, xi->x509); +- } +- sk_X509_INFO_pop_free(sk, X509_INFO_free); +- return 0; +-} +- +-static krb5_error_code +-load_http_anchor_dir(X509_STORE *store, const char *path) +-{ +- DIR *d = NULL; +- struct dirent *dentry = NULL; +- char filename[1024]; +- krb5_boolean found_any = FALSE; +- +- d = opendir(path); +- if (d == NULL) +- return ENOENT; +- while ((dentry = readdir(d)) != NULL) { +- if (dentry->d_name[0] != '.') { +- snprintf(filename, sizeof(filename), "%s/%s", +- path, dentry->d_name); +- if (load_http_anchor_file(store, filename) == 0) +- found_any = TRUE; +- } +- } +- closedir(d); +- return found_any ? 0 : ENOENT; +-} +- +-static krb5_error_code +-load_http_anchor(SSL_CTX *ctx, const char *location) ++/* Set up conn->http.tls. Return true on success. */ ++static krb5_boolean ++setup_tls(krb5_context context, const krb5_data *realm, ++ struct conn_state *conn, struct select_state *selstate) + { +- X509_STORE *store; +- const char *envloc; +- +- store = SSL_CTX_get_cert_store(ctx); +- if (strncmp(location, "FILE:", 5) == 0) { +- return load_http_anchor_file(store, location + 5); +- } else if (strncmp(location, "DIR:", 4) == 0) { +- return load_http_anchor_dir(store, location + 4); +- } else if (strncmp(location, "ENV:", 4) == 0) { +- envloc = getenv(location + 4); +- if (envloc == NULL) +- return ENOENT; +- return load_http_anchor(ctx, envloc); +- } +- return EINVAL; +-} ++ krb5_error_code ret; ++ krb5_boolean ok = FALSE; ++ char **anchors = NULL, *realmstr = NULL; ++ const char *names[4]; + +-static krb5_error_code +-load_http_verify_anchors(krb5_context context, const krb5_data *realm, +- SSL_CTX *sctx) +-{ +- const char *anchors[4]; +- char **values = NULL, *realmz; +- unsigned int i; +- krb5_error_code err; ++ if (init_tls_vtable(context) != 0 || context->tls->setup == NULL) ++ return FALSE; + +- realmz = k5memdup0(realm->data, realm->length, &err); +- if (realmz == NULL) ++ realmstr = k5memdup0(realm->data, realm->length, &ret); ++ if (realmstr == NULL) + goto cleanup; + + /* Load the configured anchors. */ +- anchors[0] = KRB5_CONF_REALMS; +- anchors[1] = realmz; +- anchors[2] = KRB5_CONF_HTTP_ANCHORS; +- anchors[3] = NULL; +- if (profile_get_values(context->profile, anchors, &values) == 0) { +- for (i = 0; values[i] != NULL; i++) { +- err = load_http_anchor(sctx, values[i]); +- if (err != 0) +- break; +- } +- profile_free_list(values); +- } else { +- /* Use the library defaults. */ +- if (SSL_CTX_set_default_verify_paths(sctx) != 1) +- err = ENOENT; +- } +- +-cleanup: +- free(realmz); +- return err; +-} +- +-static krb5_boolean +-ssl_check_name_or_ip(X509 *x, const char *expected_name) +-{ +- struct in_addr in; +- struct in6_addr in6; +- +- if (inet_aton(expected_name, &in) != 0 || +- inet_pton(AF_INET6, expected_name, &in6) != 0) { +- return k5_check_cert_address(x, expected_name); +- } else { +- return k5_check_cert_servername(x, expected_name); +- } +-} ++ names[0] = KRB5_CONF_REALMS; ++ names[1] = realmstr; ++ names[2] = KRB5_CONF_HTTP_ANCHORS; ++ names[3] = NULL; ++ ret = profile_get_values(context->profile, names, &anchors); ++ if (ret != 0 && ret != PROF_NO_RELATION) ++ goto cleanup; + +-static int +-ssl_verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx) +-{ +- X509 *x; +- SSL *ssl; +- BIO *bio; +- krb5_context context; +- int err, depth; +- struct conn_state *conn = NULL; +- const char *cert = NULL, *errstr, *expected_name; +- size_t count; +- +- ssl = X509_STORE_CTX_get_ex_data(store_ctx, +- SSL_get_ex_data_X509_STORE_CTX_idx()); +- context = SSL_get_ex_data(ssl, ssl_ex_context_id); +- conn = SSL_get_ex_data(ssl, ssl_ex_conn_id); +- /* We do have the peer's cert, right? */ +- x = X509_STORE_CTX_get_current_cert(store_ctx); +- if (x == NULL) { +- TRACE_SENDTO_KDC_HTTPS_NO_REMOTE_CERTIFICATE(context); +- return 0; +- } +- /* Figure out where we are. */ +- depth = X509_STORE_CTX_get_error_depth(store_ctx); +- if (depth < 0) +- return 0; +- /* If there's an error at this level that we're not ignoring, fail. */ +- err = X509_STORE_CTX_get_error(store_ctx); +- if (err != X509_V_OK) { +- bio = BIO_new(BIO_s_mem()); +- if (bio != NULL) { +- X509_NAME_print_ex(bio, x->cert_info->subject, 0, 0); +- count = BIO_get_mem_data(bio, &cert); +- errstr = X509_verify_cert_error_string(err); +- TRACE_SENDTO_KDC_HTTPS_PROXY_CERTIFICATE_ERROR(context, depth, +- count, cert, err, +- errstr); +- BIO_free(bio); +- } +- return 0; +- } +- /* If we're not looking at the peer, we're done and everything's ok. */ +- if (depth != 0) +- return 1; +- /* Check if the name we expect to find is in the certificate. */ +- expected_name = conn->http.servername; +- if (ssl_check_name_or_ip(x, expected_name)) { +- TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MATCH(context, expected_name); +- return 1; +- } else { +- TRACE_SENDTO_KDC_HTTPS_SERVER_NAME_MISMATCH(context, expected_name); ++ if (context->tls->setup(context, conn->fd, conn->http.servername, anchors, ++ &conn->http.tls) != 0) { ++ TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr); ++ goto cleanup; + } +- /* The name didn't match. */ +- return 0; +-} + +-/* +- * Set up structures that we use to manage the SSL handling for this connection +- * and apply any non-default settings. Kill the connection and return false if +- * anything goes wrong while we're doing that; return true otherwise. +- */ +-static krb5_boolean +-setup_ssl(krb5_context context, const krb5_data *realm, +- struct conn_state *conn, struct select_state *selstate) +-{ +- int e; +- long options; +- SSL_CTX *ctx = NULL; +- SSL *ssl = NULL; ++ ok = TRUE; + +- if (ssl_ex_context_id == -1 || ssl_ex_conn_id == -1) +- goto kill_conn; +- +- /* Do general SSL library setup. */ +- ctx = SSL_CTX_new(SSLv23_client_method()); +- if (ctx == NULL) +- goto kill_conn; +- options = SSL_CTX_get_options(ctx); +- SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2); +- +- SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, ssl_verify_callback); +- X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0); +- e = load_http_verify_anchors(context, realm, ctx); +- if (e != 0) +- goto kill_conn; +- +- ssl = SSL_new(ctx); +- if (ssl == NULL) +- goto kill_conn; +- +- if (!SSL_set_ex_data(ssl, ssl_ex_context_id, context)) +- goto kill_conn; +- if (!SSL_set_ex_data(ssl, ssl_ex_conn_id, conn)) +- goto kill_conn; +- +- /* Tell the SSL library about the socket. */ +- if (!SSL_set_fd(ssl, conn->fd)) +- goto kill_conn; +- SSL_set_connect_state(ssl); +- +- SSL_CTX_free(ctx); +- conn->http.ssl = ssl; +- +- return TRUE; +- +-kill_conn: +- TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr); +- flush_ssl_errors(context); +- SSL_free(ssl); +- SSL_CTX_free(ctx); +- kill_conn(context, conn, selstate); +- return FALSE; ++cleanup: ++ free(realmstr); ++ profile_free_list(anchors); ++ return ok; + } + + /* Set conn->state to READING when done; otherwise, call a cm_set_. */ +@@ -1395,50 +1168,41 @@ static krb5_boolean + service_https_write(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) + { +- ssize_t nwritten; +- int e; ++ k5_tls_status st; + + /* If this is our first time in here, set up the SSL context. */ +- if (conn->http.ssl == NULL && !setup_ssl(context, realm, conn, selstate)) ++ if (conn->http.tls == NULL && !setup_tls(context, realm, conn, selstate)) { ++ kill_conn(context, conn, selstate); + return FALSE; ++ } + + /* Try to transmit our request to the server. */ +- nwritten = SSL_write(conn->http.ssl, SG_BUF(conn->out.sgp), +- SG_LEN(conn->out.sgbuf)); +- if (nwritten <= 0) { +- e = SSL_get_error(conn->http.ssl, nwritten); +- if (e == SSL_ERROR_WANT_READ) { +- cm_read(selstate, conn->fd); +- return FALSE; +- } else if (e == SSL_ERROR_WANT_WRITE) { +- cm_write(selstate, conn->fd); +- return FALSE; +- } ++ st = context->tls->write(context, conn->http.tls, SG_BUF(conn->out.sgp), ++ SG_LEN(conn->out.sgbuf)); ++ if (st == DONE) { ++ TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr); ++ cm_read(selstate, conn->fd); ++ conn->state = READING; ++ } else if (st == WANT_READ) { ++ cm_read(selstate, conn->fd); ++ } else if (st == WANT_WRITE) { ++ cm_write(selstate, conn->fd); ++ } else if (st == ERROR_TLS) { + TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(context, &conn->addr); +- flush_ssl_errors(context); + kill_conn(context, conn, selstate); +- return FALSE; + } + +- /* Done writing, switch to reading. */ +- TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr); +- cm_read(selstate, conn->fd); +- conn->state = READING; + return FALSE; + } + +-/* +- * Return true on readable data, call a cm_read/write function and return +- * false if the SSL layer needs it, kill the connection otherwise. +- */ ++/* Return true on finished data. Call a cm_read/write function and return ++ * false if the TLS layer needs it. Kill the connection on error. */ + static krb5_boolean + https_read_bytes(krb5_context context, struct conn_state *conn, + struct select_state *selstate) + { +- size_t bufsize; +- ssize_t nread; +- krb5_boolean readbytes = FALSE; +- int e = 0; ++ size_t bufsize, nread; ++ k5_tls_status st; + char *tmp; + struct incoming_message *in = &conn->in; + +@@ -1458,31 +1222,26 @@ https_read_bytes(krb5_context context, struct conn_state *conn, + in->bufsize = bufsize; + } + +- nread = SSL_read(conn->http.ssl, &in->buf[in->pos], +- in->bufsize - in->pos - 1); +- if (nread <= 0) ++ st = context->tls->read(context, conn->http.tls, &in->buf[in->pos], ++ in->bufsize - in->pos - 1, &nread); ++ if (st != DATA_READ) + break; ++ + in->pos += nread; + in->buf[in->pos] = '\0'; +- readbytes = TRUE; + } + +- e = SSL_get_error(conn->http.ssl, nread); +- if (e == SSL_ERROR_WANT_READ) { ++ if (st == DONE) ++ return TRUE; ++ ++ if (st == WANT_READ) { + cm_read(selstate, conn->fd); +- return FALSE; +- } else if (e == SSL_ERROR_WANT_WRITE) { ++ } else if (st == WANT_WRITE) { + cm_write(selstate, conn->fd); +- return FALSE; +- } else if ((e == SSL_ERROR_ZERO_RETURN) || +- (e == SSL_ERROR_SYSCALL && nread == 0 && readbytes)) { +- return TRUE; ++ } else if (st == ERROR_TLS) { ++ TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr); ++ kill_conn(context, conn, selstate); + } +- +- e = readbytes ? SOCKET_ERRNO : ECONNRESET; +- TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr, e); +- flush_ssl_errors(context); +- kill_conn(context, conn, selstate); + return FALSE; + } + +@@ -1531,20 +1290,6 @@ kill_conn: + kill_conn(context, conn, selstate); + return FALSE; + } +-#else +-static krb5_boolean +-service_https_write(krb5_context context, const krb5_data *realm, +- struct conn_state *conn, struct select_state *selstate) +-{ +- abort(); +-} +-static krb5_boolean +-service_https_read(krb5_context context, const krb5_data *realm, +- struct conn_state *conn, struct select_state *selstate) +-{ +- abort(); +-} +-#endif + + /* Return the maximum of endtime and the endtime fields of all currently active + * TCP connections. */ +@@ -1765,7 +1510,7 @@ cleanup: + if (socktype_for_transport(state->addr.transport) == SOCK_STREAM) + TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &state->addr); + closesocket(state->fd); +- free_http_ssl_data(state); ++ free_http_tls_data(context, state); + } + if (state->state == READING && state->in.buf != udpbuf) + free(state->in.buf); +diff --git a/src/plugins/tls/k5tls/Makefile.in b/src/plugins/tls/k5tls/Makefile.in +new file mode 100644 +index 0000000..4d58df0 +--- /dev/null ++++ b/src/plugins/tls/k5tls/Makefile.in +@@ -0,0 +1,22 @@ ++mydir=plugins$(S)tls$(S)k5tls ++BUILDTOP=$(REL)..$(S)..$(S).. ++MODULE_INSTALL_DIR = $(KRB5_TLS_MODULE_DIR) ++LOCALINCLUDES= $(PROXY_TLS_IMPL_CFLAGS) ++ ++LIBBASE=k5tls ++LIBMAJOR=0 ++LIBMINOR=0 ++RELDIR=../plugins/tls/k5tls ++SHLIB_EXPDEPS= $(KRB5_DEPLIB) $(SUPPORT_DEPLIB) ++SHLIB_EXPLIBS= $(KRB5_LIB) $(SUPPORT_LIB) $(PROXY_TLS_IMPL_LIBS) ++ ++STLIBOBJS=openssl.o notls.o ++ ++SRCS=$(srcdir)/openssl.c $(srcdir)/notls.c ++ ++all-unix:: all-liblinks ++install-unix:: install-libs ++clean-unix:: clean-libs clean-libobjs ++ ++@libnover_frag@ ++@libobj_frag@ +diff --git a/src/plugins/tls/k5tls/deps b/src/plugins/tls/k5tls/deps +new file mode 100644 +index 0000000..a6088a7 +--- /dev/null ++++ b/src/plugins/tls/k5tls/deps +@@ -0,0 +1,25 @@ ++# ++# Generated makefile dependencies follow. ++# ++openssl.so openssl.po $(OUTPRE)openssl.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ ++ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ ++ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ ++ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \ ++ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \ ++ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ ++ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-tls.h \ ++ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/k5-utf8.h \ ++ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ ++ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ ++ $(top_srcdir)/include/socket-utils.h openssl.c ++notls.so notls.po $(OUTPRE)notls.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ ++ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ ++ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ ++ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \ ++ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \ ++ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ ++ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-tls.h \ ++ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/k5-utf8.h \ ++ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ ++ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ ++ $(top_srcdir)/include/socket-utils.h notls.c +diff --git a/src/plugins/tls/k5tls/k5tls.exports b/src/plugins/tls/k5tls/k5tls.exports +new file mode 100644 +index 0000000..d67d928 +--- /dev/null ++++ b/src/plugins/tls/k5tls/k5tls.exports +@@ -0,0 +1 @@ ++tls_k5tls_initvt +diff --git a/src/plugins/tls/k5tls/notls.c b/src/plugins/tls/k5tls/notls.c +new file mode 100644 +index 0000000..7be0a4a +--- /dev/null ++++ b/src/plugins/tls/k5tls/notls.c +@@ -0,0 +1,53 @@ ++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ ++/* plus/tls/k5tls/none.c - Stub TLS module implementation */ ++/* ++ * Copyright (C) 2014 by the Massachusetts Institute of Technology. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in ++ * the documentation and/or other materials provided with the ++ * distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS ++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, ++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, ++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED ++ * OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++/* This dummy module is used if no TLS implemented is selected. */ ++ ++#include "k5-int.h" ++#include "k5-utf8.h" ++#include "k5-tls.h" ++ ++#ifdef PROXY_TLS_IMPL_NONE ++ ++krb5_error_code ++tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver, ++ krb5_plugin_vtable vtable); ++ ++krb5_error_code ++tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver, ++ krb5_plugin_vtable vtable) ++{ ++ /* Leave all vtable functions nulled. */ ++ return 0; ++} ++ ++#endif /* PROXY_TLS_IMPL_NONE */ +diff --git a/src/plugins/tls/k5tls/openssl.c b/src/plugins/tls/k5tls/openssl.c +new file mode 100644 +index 0000000..0691a34 +--- /dev/null ++++ b/src/plugins/tls/k5tls/openssl.c +@@ -0,0 +1,570 @@ ++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ ++/* plugins/tls/k5tls/openssl.c - OpenSSL TLS module implementation */ ++/* ++ * Copyright 2013,2014 Red Hat, Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in ++ * the documentation and/or other materials provided with the ++ * distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS ++ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED ++ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER ++ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "k5-int.h" ++#include "k5-utf8.h" ++#include "k5-tls.h" ++ ++#ifdef PROXY_TLS_IMPL_OPENSSL ++#include ++#include ++#include ++#include ++#include ++ ++struct k5_tls_handle_st { ++ SSL *ssl; ++ char *servername; ++}; ++ ++static int ex_context_id = -1; ++static int ex_handle_id = -1; ++ ++MAKE_INIT_FUNCTION(init_openssl); ++ ++int ++init_openssl() ++{ ++ SSL_library_init(); ++ SSL_load_error_strings(); ++ OpenSSL_add_all_algorithms(); ++ ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); ++ ex_handle_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); ++ return 0; ++} ++ ++static void ++flush_errors(krb5_context context) ++{ ++ unsigned long err; ++ char buf[128]; ++ ++ while ((err = ERR_get_error()) != 0) { ++ ERR_error_string_n(err, buf, sizeof(buf)); ++ TRACE_TLS_ERROR(context, buf); ++ } ++} ++ ++/* Return the passed-in character, lower-cased if it's an ASCII character. */ ++static inline char ++ascii_tolower(char p) ++{ ++ if (KRB5_UPPER(p)) ++ return p + ('a' - 'A'); ++ return p; ++} ++ ++/* ++ * Check a single label. If allow_wildcard is true, and the presented name ++ * includes a wildcard, return true and note that we matched a wildcard. ++ * Otherwise, for both the presented and expected values, do a case-insensitive ++ * comparison of ASCII characters, and a case-sensitive comparison of ++ * everything else. ++ */ ++static krb5_boolean ++label_match(const char *presented, size_t plen, const char *expected, ++ size_t elen, krb5_boolean allow_wildcard, krb5_boolean *wildcard) ++{ ++ unsigned int i; ++ ++ if (allow_wildcard && plen == 1 && presented[0] == '*') { ++ *wildcard = TRUE; ++ return TRUE; ++ } ++ ++ if (plen != elen) ++ return FALSE; ++ ++ for (i = 0; i < elen; i++) { ++ if (ascii_tolower(presented[i]) != ascii_tolower(expected[i])) ++ return FALSE; ++ } ++ return TRUE; ++} ++ ++/* Break up the two names and check them, label by label. */ ++static krb5_boolean ++domain_match(const char *presented, size_t plen, const char *expected) ++{ ++ const char *p, *q, *r, *s; ++ int n_label; ++ krb5_boolean used_wildcard = FALSE; ++ ++ n_label = 0; ++ p = presented; ++ r = expected; ++ while (p < presented + plen && *r != '\0') { ++ q = memchr(p, '.', plen - (p - presented)); ++ if (q == NULL) ++ q = presented + plen; ++ s = r + strcspn(r, "."); ++ if (!label_match(p, q - p, r, s - r, n_label == 0, &used_wildcard)) ++ return FALSE; ++ p = q < presented + plen ? q + 1 : q; ++ r = *s ? s + 1 : s; ++ n_label++; ++ } ++ if (used_wildcard && n_label <= 2) ++ return FALSE; ++ if (p == presented + plen && *r == '\0') ++ return TRUE; ++ return FALSE; ++} ++ ++/* Fetch the list of subjectAltNames from a certificate. */ ++static GENERAL_NAMES * ++get_cert_sans(X509 *x) ++{ ++ int ext; ++ X509_EXTENSION *san_ext; ++ ++ ext = X509_get_ext_by_NID(x, NID_subject_alt_name, -1); ++ if (ext < 0) ++ return NULL; ++ san_ext = X509_get_ext(x, ext); ++ if (san_ext == NULL) ++ return NULL; ++ return X509V3_EXT_d2i(san_ext); ++} ++ ++/* Fetch a CN value from the subjct name field, returning its length, or -1 if ++ * there is no subject name or it contains no CN value. */ ++static int ++get_cert_cn(X509 *x, char *buf, size_t bufsize) ++{ ++ X509_NAME *name; ++ ++ name = X509_get_subject_name(x); ++ if (name == NULL) ++ return -1; ++ return X509_NAME_get_text_by_NID(name, NID_commonName, buf, bufsize); ++} ++ ++/* Return true if text matches any of the addresses we can recover from x. */ ++static krb5_boolean ++check_cert_address(X509 *x, const char *text) ++{ ++ char buf[1024]; ++ GENERAL_NAMES *sans; ++ GENERAL_NAME *san = NULL; ++ ASN1_OCTET_STRING *ip; ++ krb5_boolean found_ip_san = FALSE, matched = FALSE; ++ int n_sans, i; ++ int name_length; ++ struct in_addr sin; ++ struct in6_addr sin6; ++ ++ /* Parse the IP address into an octet string. */ ++ ip = M_ASN1_OCTET_STRING_new(); ++ if (ip == NULL) ++ return FALSE; ++ if (inet_pton(AF_INET, text, &sin)) { ++ M_ASN1_OCTET_STRING_set(ip, &sin, sizeof(sin)); ++ } else if (inet_pton(AF_INET6, text, &sin6)) { ++ M_ASN1_OCTET_STRING_set(ip, &sin6, sizeof(sin6)); ++ } else { ++ ASN1_OCTET_STRING_free(ip); ++ return FALSE; ++ } ++ ++ /* Check for matches in ipaddress subjectAltName values. */ ++ sans = get_cert_sans(x); ++ if (sans != NULL) { ++ n_sans = sk_GENERAL_NAME_num(sans); ++ for (i = 0; i < n_sans; i++) { ++ san = sk_GENERAL_NAME_value(sans, i); ++ if (san->type != GEN_IPADD) ++ continue; ++ found_ip_san = TRUE; ++ matched = (ASN1_OCTET_STRING_cmp(ip, san->d.iPAddress) == 0); ++ if (matched) ++ break; ++ } ++ sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free); ++ } ++ ASN1_OCTET_STRING_free(ip); ++ ++ if (found_ip_san) ++ return matched; ++ ++ /* Check for a match against the CN value in the peer's subject name. */ ++ name_length = get_cert_cn(x, buf, sizeof(buf)); ++ if (name_length >= 0) { ++ /* Do a string compare to check if it's an acceptable value. */ ++ return strlen(text) == (size_t)name_length && ++ strncmp(text, buf, name_length) == 0; ++ } ++ ++ /* We didn't find a match. */ ++ return FALSE; ++} ++ ++/* Return true if expected matches any of the names we can recover from x. */ ++static krb5_boolean ++check_cert_servername(X509 *x, const char *expected) ++{ ++ char buf[1024]; ++ GENERAL_NAMES *sans; ++ GENERAL_NAME *san = NULL; ++ unsigned char *dnsname; ++ krb5_boolean found_dns_san = FALSE, matched = FALSE; ++ int name_length, n_sans, i; ++ ++ /* Check for matches in dnsname subjectAltName values. */ ++ sans = get_cert_sans(x); ++ if (sans != NULL) { ++ n_sans = sk_GENERAL_NAME_num(sans); ++ for (i = 0; i < n_sans; i++) { ++ san = sk_GENERAL_NAME_value(sans, i); ++ if (san->type != GEN_DNS) ++ continue; ++ found_dns_san = TRUE; ++ dnsname = NULL; ++ name_length = ASN1_STRING_to_UTF8(&dnsname, san->d.dNSName); ++ if (dnsname == NULL) ++ continue; ++ matched = domain_match((char *)dnsname, name_length, expected); ++ OPENSSL_free(dnsname); ++ if (matched) ++ break; ++ } ++ sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free); ++ } ++ ++ if (matched) ++ return TRUE; ++ if (found_dns_san) ++ return matched; ++ ++ /* Check for a match against the CN value in the peer's subject name. */ ++ name_length = get_cert_cn(x, buf, sizeof(buf)); ++ if (name_length >= 0) ++ return domain_match(buf, name_length, expected); ++ ++ /* We didn't find a match. */ ++ return FALSE; ++} ++ ++static krb5_boolean ++check_cert_name_or_ip(X509 *x, const char *expected_name) ++{ ++ struct in_addr in; ++ struct in6_addr in6; ++ ++ if (inet_pton(AF_INET, expected_name, &in) != 0 || ++ inet_pton(AF_INET6, expected_name, &in6) != 0) { ++ return check_cert_address(x, expected_name); ++ } else { ++ return check_cert_servername(x, expected_name); ++ } ++} ++ ++static int ++verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx) ++{ ++ X509 *x; ++ SSL *ssl; ++ BIO *bio; ++ krb5_context context; ++ int err, depth; ++ k5_tls_handle handle; ++ const char *cert = NULL, *errstr, *expected_name; ++ size_t count; ++ ++ ssl = X509_STORE_CTX_get_ex_data(store_ctx, ++ SSL_get_ex_data_X509_STORE_CTX_idx()); ++ context = SSL_get_ex_data(ssl, ex_context_id); ++ handle = SSL_get_ex_data(ssl, ex_handle_id); ++ assert(context != NULL && handle != NULL); ++ /* We do have the peer's cert, right? */ ++ x = X509_STORE_CTX_get_current_cert(store_ctx); ++ if (x == NULL) { ++ TRACE_TLS_NO_REMOTE_CERTIFICATE(context); ++ return 0; ++ } ++ /* Figure out where we are. */ ++ depth = X509_STORE_CTX_get_error_depth(store_ctx); ++ if (depth < 0) ++ return 0; ++ /* If there's an error at this level that we're not ignoring, fail. */ ++ err = X509_STORE_CTX_get_error(store_ctx); ++ if (err != X509_V_OK) { ++ bio = BIO_new(BIO_s_mem()); ++ if (bio != NULL) { ++ X509_NAME_print_ex(bio, x->cert_info->subject, 0, 0); ++ count = BIO_get_mem_data(bio, &cert); ++ errstr = X509_verify_cert_error_string(err); ++ TRACE_TLS_CERT_ERROR(context, depth, count, cert, err, errstr); ++ BIO_free(bio); ++ } ++ return 0; ++ } ++ /* If we're not looking at the peer, we're done and everything's ok. */ ++ if (depth != 0) ++ return 1; ++ /* Check if the name we expect to find is in the certificate. */ ++ expected_name = handle->servername; ++ if (check_cert_name_or_ip(x, expected_name)) { ++ TRACE_TLS_SERVER_NAME_MATCH(context, expected_name); ++ return 1; ++ } else { ++ TRACE_TLS_SERVER_NAME_MISMATCH(context, expected_name); ++ } ++ /* The name didn't match. */ ++ return 0; ++} ++ ++static krb5_error_code ++load_anchor_file(X509_STORE *store, const char *path) ++{ ++ FILE *fp; ++ STACK_OF(X509_INFO) *sk = NULL; ++ X509_INFO *xi; ++ int i; ++ ++ fp = fopen(path, "r"); ++ if (fp == NULL) ++ return errno; ++ sk = PEM_X509_INFO_read(fp, NULL, NULL, NULL); ++ fclose(fp); ++ if (sk == NULL) ++ return ENOENT; ++ for (i = 0; i < sk_X509_INFO_num(sk); i++) { ++ xi = sk_X509_INFO_value(sk, i); ++ if (xi->x509 != NULL) ++ X509_STORE_add_cert(store, xi->x509); ++ } ++ sk_X509_INFO_pop_free(sk, X509_INFO_free); ++ return 0; ++} ++ ++static krb5_error_code ++load_anchor_dir(X509_STORE *store, const char *path) ++{ ++ DIR *d = NULL; ++ struct dirent *dentry = NULL; ++ char filename[1024]; ++ krb5_boolean found_any = FALSE; ++ ++ d = opendir(path); ++ if (d == NULL) ++ return ENOENT; ++ while ((dentry = readdir(d)) != NULL) { ++ if (dentry->d_name[0] != '.') { ++ snprintf(filename, sizeof(filename), "%s/%s", ++ path, dentry->d_name); ++ if (load_anchor_file(store, filename) == 0) ++ found_any = TRUE; ++ } ++ } ++ closedir(d); ++ return found_any ? 0 : ENOENT; ++} ++ ++static krb5_error_code ++load_anchor(SSL_CTX *ctx, const char *location) ++{ ++ X509_STORE *store; ++ const char *envloc; ++ ++ store = SSL_CTX_get_cert_store(ctx); ++ if (strncmp(location, "FILE:", 5) == 0) { ++ return load_anchor_file(store, location + 5); ++ } else if (strncmp(location, "DIR:", 4) == 0) { ++ return load_anchor_dir(store, location + 4); ++ } else if (strncmp(location, "ENV:", 4) == 0) { ++ envloc = getenv(location + 4); ++ if (envloc == NULL) ++ return ENOENT; ++ return load_anchor(ctx, envloc); ++ } ++ return EINVAL; ++} ++ ++static krb5_error_code ++load_anchors(krb5_context context, char **anchors, SSL_CTX *sctx) ++{ ++ unsigned int i; ++ krb5_error_code ret; ++ ++ if (anchors != NULL) { ++ for (i = 0; anchors[i] != NULL; i++) { ++ ret = load_anchor(sctx, anchors[i]); ++ if (ret) ++ return ret; ++ } ++ } else { ++ /* Use the library defaults. */ ++ if (SSL_CTX_set_default_verify_paths(sctx) != 1) ++ return ENOENT; ++ } ++ ++ return 0; ++} ++ ++static krb5_error_code ++setup(krb5_context context, SOCKET fd, const char *servername, ++ char **anchors, k5_tls_handle *handle_out) ++{ ++ int e; ++ long options; ++ SSL_CTX *ctx = NULL; ++ SSL *ssl = NULL; ++ k5_tls_handle handle = NULL; ++ ++ *handle_out = NULL; ++ ++ (void)CALL_INIT_FUNCTION(init_openssl); ++ if (ex_context_id == -1 || ex_handle_id == -1) ++ return KRB5_PLUGIN_OP_NOTSUPP; ++ ++ /* Do general SSL library setup. */ ++ ctx = SSL_CTX_new(SSLv23_client_method()); ++ if (ctx == NULL) ++ goto error; ++ options = SSL_CTX_get_options(ctx); ++ SSL_CTX_set_options(ctx, options | SSL_OP_NO_SSLv2); ++ ++ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); ++ X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0); ++ e = load_anchors(context, anchors, ctx); ++ if (e != 0) ++ goto error; ++ ++ ssl = SSL_new(ctx); ++ if (ssl == NULL) ++ goto error; ++ ++ if (!SSL_set_fd(ssl, fd)) ++ goto error; ++ SSL_set_connect_state(ssl); ++ ++ /* Create a handle and allow verify_callback to access it. */ ++ handle = malloc(sizeof(*handle)); ++ if (handle == NULL || !SSL_set_ex_data(ssl, ex_handle_id, handle)) ++ goto error; ++ ++ handle->ssl = ssl; ++ handle->servername = strdup(servername); ++ if (handle->servername == NULL) ++ goto error; ++ *handle_out = handle; ++ SSL_CTX_free(ctx); ++ return 0; ++ ++error: ++ flush_errors(context); ++ free(handle); ++ SSL_free(ssl); ++ SSL_CTX_free(ctx); ++ return KRB5_PLUGIN_OP_NOTSUPP; ++} ++ ++static k5_tls_status ++write_tls(krb5_context context, k5_tls_handle handle, const void *data, ++ size_t len) ++{ ++ int nwritten, e; ++ ++ /* Try to transmit our request; allow verify_callback to access context. */ ++ if (!SSL_set_ex_data(handle->ssl, ex_context_id, context)) ++ return ERROR_TLS; ++ nwritten = SSL_write(handle->ssl, data, len); ++ (void)SSL_set_ex_data(handle->ssl, ex_context_id, NULL); ++ if (nwritten > 0) ++ return DONE; ++ ++ e = SSL_get_error(handle->ssl, nwritten); ++ if (e == SSL_ERROR_WANT_READ) ++ return WANT_READ; ++ else if (e == SSL_ERROR_WANT_WRITE) ++ return WANT_WRITE; ++ flush_errors(context); ++ return ERROR_TLS; ++} ++ ++static k5_tls_status ++read_tls(krb5_context context, k5_tls_handle handle, void *data, ++ size_t data_size, size_t *len_out) ++{ ++ ssize_t nread; ++ int e; ++ ++ *len_out = 0; ++ ++ /* Try to read response data; allow verify_callback to access context. */ ++ if (!SSL_set_ex_data(handle->ssl, ex_context_id, context)) ++ return ERROR_TLS; ++ nread = SSL_read(handle->ssl, data, data_size); ++ (void)SSL_set_ex_data(handle->ssl, ex_context_id, NULL); ++ if (nread > 0) { ++ *len_out = nread; ++ return DATA_READ; ++ } ++ ++ e = SSL_get_error(handle->ssl, nread); ++ if (e == SSL_ERROR_WANT_READ) ++ return WANT_READ; ++ else if (e == SSL_ERROR_WANT_WRITE) ++ return WANT_WRITE; ++ ++ if (e == SSL_ERROR_ZERO_RETURN || (e == SSL_ERROR_SYSCALL && nread == 0)) ++ return DONE; ++ ++ flush_errors(context); ++ return ERROR_TLS; ++} ++ ++static void ++free_handle(krb5_context context, k5_tls_handle handle) ++{ ++ SSL_free(handle->ssl); ++ free(handle->servername); ++ free(handle); ++} ++ ++krb5_error_code ++tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver, ++ krb5_plugin_vtable vtable); ++ ++krb5_error_code ++tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver, ++ krb5_plugin_vtable vtable) ++{ ++ k5_tls_vtable vt; ++ ++ vt = (k5_tls_vtable)vtable; ++ vt->setup = setup; ++ vt->write = write_tls; ++ vt->read = read_tls; ++ vt->free_handle = free_handle; ++ return 0; ++} ++ ++#endif /* PROXY_TLS_IMPL_OPENSSL */ diff --git a/krb5-master-move-otp-sockets.patch b/krb5-master-move-otp-sockets.patch index 350a818..8d86930 100644 --- a/krb5-master-move-otp-sockets.patch +++ b/krb5-master-move-otp-sockets.patch @@ -93,7 +93,7 @@ index a8bc990..1725093 100644 +++ b/src/Makefile.in @@ -64,6 +64,7 @@ INSTALLMKDIRS = $(KRB5ROOT) $(KRB5MANROOT) $(KRB5OTHERMKDIRS) \ $(KRB5_AD_MODULE_DIR) \ - $(KRB5_LIBKRB5_MODULE_DIR) \ + $(KRB5_LIBKRB5_MODULE_DIR) $(KRB5_TLS_MODULE_DIR) \ @localstatedir@ @localstatedir@/krb5kdc \ + @runstatedir@ @runstatedir@/krb5kdc \ $(KRB5_INCSUBDIRS) $(datadir) $(EXAMPLEDIR) \ diff --git a/krb5.spec b/krb5.spec index 393c0c2..17cff94 100644 --- a/krb5.spec +++ b/krb5.spec @@ -84,7 +84,7 @@ Patch23: krb5-1.3.1-dns.patch Patch29: krb5-1.10-kprop-mktemp.patch Patch30: krb5-1.3.4-send-pr-tempfile.patch Patch39: krb5-1.12-api.patch -Patch59: krb5-1.10-kpasswd_tcp.patch +Patch59: krb5-1.12ish-kpasswd_tcp.patch Patch60: krb5-1.12.1-pam.patch Patch63: krb5-1.12-selinux-label.patch Patch71: krb5-1.11-dirsrv-accountlock.patch @@ -109,6 +109,21 @@ Patch205: 0005-Copy-config-entries-to-the-ksu-target-ccache.patch Patch206: 0006-Use-more-randomness-for-ksu-secondary-cache-names.patch Patch207: 0007-Make-krb5_cc_new_unique-create-DIR-directories.patch Patch300: krb5-1.12-kpasswd-skip-address-check.patch +Patch301: 0000-Refactor-cm-functions-in-sendto_kdc.c.patch +Patch302: 0001-Simplify-sendto_kdc.c.patch +Patch303: 0002-Add-helper-to-determine-if-a-KDC-is-the-master.patch +Patch304: 0003-Use-k5_transport-_strategy-enums-for-k5_sendto.patch +Patch305: 0004-Build-support-for-TLS-used-by-HTTPS-proxy-support.patch +Patch306: 0005-Add-ASN.1-codec-for-KKDCP-s-KDC-PROXY-MESSAGE.patch +Patch307: 0006-Dispatch-style-protocol-switching-for-transport.patch +Patch308: 0007-HTTPS-transport-Microsoft-KKDCPP-implementation.patch +Patch309: 0008-Load-custom-anchors-when-using-KKDCP.patch +Patch310: 0009-Check-names-in-the-server-s-cert-when-using-KKDCP.patch +Patch311: 0010-Add-some-longer-form-docs-for-HTTPS.patch +Patch312: 0011-Have-k5test.py-provide-runenv-to-python-tests.patch +Patch313: 0012-Add-a-simple-KDC-proxy-test-server.patch +Patch314: 0013-Add-tests-for-MS-KKDCP-client-support.patch +Patch315: krb5-1.12ish-tls-plugins.patch License: MIT URL: http://web.mit.edu/kerberos/www/ @@ -320,6 +335,22 @@ ln -s NOTICE LICENSE %patch207 -p1 -b .Make-krb5_cc_new_unique-create-DIR-directories %patch300 -p1 -b .kpasswd-skip-address-check +%patch301 -p1 -b .Refactor-cm-functions-in-sendto_kdc.c +%patch302 -p1 -b .Simplify-sendto_kdc.c +%patch303 -p1 -b .Add-helper-to-determine-if-a-KDC-is-the-master +%patch304 -p1 -b .Use-k5_transport-_strategy-enums-for-k5_sendto +%patch305 -p1 -b .Build-support-for-TLS-used-by-HTTPS-proxy-support +%patch306 -p1 -b .Add-ASN.1-codec-for-KKDCP-s-KDC-PROXY-MESSAGE +%patch307 -p1 -b .Dispatch-style-protocol-switching-for-transport +%patch308 -p1 -b .HTTPS-transport-Microsoft-KKDCPP-implementation +%patch309 -p1 -b .Load-custom-anchors-when-using-KKDCP +%patch310 -p1 -b .Check-names-in-the-server-s-cert-when-using-KKDCP +%patch311 -p1 -b .Add-some-longer-form-docs-for-HTTPS +%patch312 -p1 -b .Have-k5test.py-provide-runenv-to-python-tests +%patch313 -p1 -b .Add-a-simple-KDC-proxy-test-server +%patch314 -p1 -b .Add-tests-for-MS-KKDCP-client-support +%patch315 -p1 -b .tls-plugins +chmod u+x src/util/paste-kdcproxy.py %patch1 -p1 -b .pwdch-fast @@ -630,6 +661,7 @@ done # Plug-in directories. install -pdm 755 $RPM_BUILD_ROOT/%{_libdir}/krb5/plugins/preauth install -pdm 755 $RPM_BUILD_ROOT/%{_libdir}/krb5/plugins/kdb +install -pdm 755 $RPM_BUILD_ROOT/%{_libdir}/krb5/plugins/tls install -pdm 755 $RPM_BUILD_ROOT/%{_libdir}/krb5/plugins/authdata # The rest of the binaries, headers, libraries, and docs. @@ -967,6 +999,7 @@ exit 0 %dir %{_libdir}/krb5 %dir %{_libdir}/krb5/plugins %dir %{_libdir}/krb5/plugins/* +%{_libdir}/krb5/plugins/tls/k5tls.so %{_libdir}/krb5/plugins/kdb/db2.so %dir %{_var}/kerberos %dir %{_var}/kerberos/krb5 @@ -1036,6 +1069,11 @@ exit 0 between NAT and upcoming HTTPS support, can cause us to erroneously report an error to the user when the server actually reported success (RT #7886) +- backport support for accessing KDCs and kpasswd services via HTTPS + proxies (marked by being specified as https URIs instead as hostnames + or hostname-and-port), such as the one implemented in python-kdcproxy + (RT #7929, #109919), and pick up a subsequent patch to build HTTPS + as a plugin * Thu Aug 28 2014 Nalin Dahyabhai - 1.12.2-5 - backport fix for trying all compatible keys when not being strict about -- cgit