From 842ca62336cd44b6ed1add2c93bf7a7649c58f08 Mon Sep 17 00:00:00 2001 From: "Roland C. Dowdeswell" Date: Thu, 12 Jul 2012 01:00:23 +0100 Subject: [PATCH] Ensure that timeouts apply to TCP socket connexions. Currently the Heimdal code calls connect(2) on TCP connexions to the KDC without setting O_NONBLOCK. This code implements a timed_connect() function which will in the case of SOCK_STREAM sockets put the socket into non-blocking mode prior to calling connect and use select(2) to apply the configured timeout to connect completion. This does not entirely solve the problem of potential timeouts in the code as it is still possible to block while writing to the socket. A proper implementation would also likely start new connexions after a short interval before timing out existing connexions and return the results from the first KDC which successfully responds but we did not do that yet. This patch is from heimdal-1-5-branch patches: 5b55e4429caed27b32aac4bc5930f2672a43f273 6b66321b271ee4672e70ad349ec796dd755cf897 2e12c7f3e8dca7e1696ebd92199617ce413565e7 Squashed together along with a quick shadowed variable warning fix to allow it to compile with --enable-developer. --- lib/krb5/krb5_locl.h | 2 ++ lib/krb5/send_to_kdc.c | 55 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/krb5/krb5_locl.h b/lib/krb5/krb5_locl.h index eec9c97b2..6ce001dbd 100644 --- a/lib/krb5/krb5_locl.h +++ b/lib/krb5/krb5_locl.h @@ -47,6 +47,8 @@ #include #include +#include + #include #ifdef HAVE_SYS_TYPES_H diff --git a/lib/krb5/send_to_kdc.c b/lib/krb5/send_to_kdc.c index a4f5434d7..25160bdf5 100644 --- a/lib/krb5/send_to_kdc.c +++ b/lib/krb5/send_to_kdc.c @@ -39,6 +39,57 @@ struct send_to_kdc { void *data; }; +/* + * connect to a remote host and in the case of stream sockets, provide + * a timeout for the connexion. + */ + +static int +timed_connect(int s, struct addrinfo *addr, time_t tmout) +{ + socklen_t sl; + int so_err; + int flags; + int ret; + + if (addr->ai_socktype != SOCK_STREAM) + return connect(s, addr->ai_addr, addr->ai_addrlen); + + flags = fcntl(s, F_GETFL); + if (flags == -1) + return -1; + + fcntl(s, F_SETFL, flags | O_NONBLOCK); + ret = connect(s, addr->ai_addr, addr->ai_addrlen); + if (ret == -1 && errno != EINPROGRESS) + return -1; + + for (;;) { + struct pollfd fds; + + fds.fd = s; + fds.events = POLLIN | POLLOUT; + fds.revents = 0; + + ret = poll(&fds, 1, tmout * 1000); + if (ret != -1 || errno != EINTR) + break; + } + fcntl(s, F_SETFL, flags); + + if (ret != 1) + return -1; + + sl = sizeof(so_err); + ret = getsockopt(s, SOL_SOCKET, SO_ERROR, &so_err, &sl); + if (ret == -1) + return -1; + if (so_err != 0) + return -1; + + return 0; +} + /* * send the data in `req' on the socket `fd' (which is datagram iff udp) * waiting `tmout' for a reply and returning the reply in `rep'. @@ -293,7 +344,7 @@ send_via_proxy (krb5_context context, if (s < 0) continue; rk_cloexec(s); - if (connect (s, a->ai_addr, a->ai_addrlen) < 0) { + if (timed_connect (s, a, context->kdc_timeout) < 0) { rk_closesocket (s); continue; } @@ -420,7 +471,7 @@ krb5_sendto (krb5_context context, if (rk_IS_BAD_SOCKET(fd)) continue; rk_cloexec(fd); - if (connect (fd, a->ai_addr, a->ai_addrlen) < 0) { + if (timed_connect (fd, a, context->kdc_timeout) < 0) { rk_closesocket (fd); continue; }