diff options
author | Nalin Dahyabhai <nalin@redhat.com> | 2014-09-05 17:51:35 -0400 |
---|---|---|
committer | Nalin Dahyabhai <nalin@redhat.com> | 2014-09-05 18:18:58 -0400 |
commit | 888bc144da94c9bf8d2c35ab38868e748c059de3 (patch) | |
tree | 75f2892ca8e4b049bbdce82ffd200cf66dea81bc /0007-HTTPS-transport-Microsoft-KKDCPP-implementation.patch | |
parent | f69697ba82697909efed00f34c51901f881e1989 (diff) | |
download | krb5-888bc144da94c9bf8d2c35ab38868e748c059de3.tar.gz krb5-888bc144da94c9bf8d2c35ab38868e748c059de3.tar.xz krb5-888bc144da94c9bf8d2c35ab38868e748c059de3.zip |
Add HTTPS patches from masterkrb5-1.12.2-6.fc21
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.
Diffstat (limited to '0007-HTTPS-transport-Microsoft-KKDCPP-implementation.patch')
-rw-r--r-- | 0007-HTTPS-transport-Microsoft-KKDCPP-implementation.patch | 858 |
1 files changed, 858 insertions, 0 deletions
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 <nalin@dahyabhai.net> +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 <openssl/err.h> + #include <openssl/ssl.h> + #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 + |