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