krb5 commit: HTTPS transport (Microsoft KKDCPP implementation)

Greg Hudson ghudson at MIT.EDU
Mon Jun 2 18:53:31 EDT 2014


https://github.com/krb5/krb5/commit/d950809ff49e3e7603594186d77135a09ab6b1b2
commit d950809ff49e3e7603594186d77135a09ab6b1b2
Author: Nalin Dahyabhai <nalin at dahyabhai.net>
Date:   Thu Apr 24 16:30:56 2014 -0400

    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 at 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);
 


More information about the cvs-krb5 mailing list