diff --git a/lib/krb5/changepw.c b/lib/krb5/changepw.c index 22a1f404c..d040380c2 100644 --- a/lib/krb5/changepw.c +++ b/lib/krb5/changepw.c @@ -32,6 +32,7 @@ */ #include "krb5_locl.h" +#include "socks4a.h" #undef __attribute__ #define __attribute__(X) @@ -501,6 +502,38 @@ static const struct kpwd_proc { { NULL, 0, NULL, NULL } }; +/* + * Proxy I/O + */ + +static int +chgpw_socks4a_read(void *vcontext, void *vsockp, void *buf, unsigned len) +{ + int *sockp = vsockp; + + return recv(*sockp, buf, len, 0); +} + +static int +chgpw_socks4a_write(void *vcontext, void *vsockp, const void *buf, + unsigned len) +{ + int *sockp = vsockp; + + return send(*sockp, buf, len, 0); +} + +static struct socks4a_io +chgpw_socks4a_io(int *sockp) +{ + + return (struct socks4a_io) { + .sio_cookie = sockp, + .sio_read = &chgpw_socks4a_read, + .sio_write = &chgpw_socks4a_write, + }; +} + /* * */ @@ -519,6 +552,8 @@ change_password_loop (krb5_context context, krb5_auth_context auth_context = NULL; krb5_krbhst_handle handle = NULL; krb5_krbhst_info *hi; + struct addrinfo *proxy_ai = NULL; + char *proxy_userid = NULL; rk_socket_t sock; unsigned int i; int done = 0; @@ -540,6 +575,62 @@ change_password_loop (krb5_context context, if (ret) goto out; + /* + * If we're configured to talk through a SOCKS4a proxy, resolve its + * address first so we can reuse it in the loop over hosts. + */ + if (context->socks4a_proxy) { + char *proxy, *proxyhost, *proxyport; + struct addrinfo hints; + + /* + * Parse `[:]' into parts. + */ + if ((proxy = strdup(context->socks4a_proxy)) == NULL) { + ret = ENOMEM; + goto out; + } + proxyhost = proxy; + if ((proxyport = strchr(proxy, ':')) != NULL) + *proxyport++ = '\0'; + + /* + * Set up getaddrinfo hints for stream connection. + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + /* + * If block_dns is enabled, make sure AI_CANONNAME is clear and + * AI_NUMERICHOST|AI_NUMERICSERV are both set to avoid the + * potential for DNS leaks. + */ + if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns", + NULL)) { + hints.ai_flags &= ~AI_CANONNAME; + hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV; + } + + /* + * Resolve the proxy's address. + */ + ret = getaddrinfo(proxyhost, proxyport ? proxyport : "1080", &hints, + &proxy_ai); + free(proxy); + if (ret) { + ret = krb5_eai_to_heim_errno(ret, errno); + goto out; + } + + /* + * Get the userid for stream isolation. + */ + ret = krb5_unparse_name(context, targprinc, &proxy_userid); + if (ret) + goto out; + } + while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) { struct addrinfo *ai, *a; int is_stream; @@ -577,6 +668,55 @@ change_password_loop (krb5_context context, goto out; } + /* + * If there's a SOCKS4a proxy in the way, we need the proxy + * to connect to the real host before we can continue. + */ + if (context->socks4a_proxy) { + struct socks4a *socks4a; + + /* + * Set up the SOCKS4a proxy connection request. + */ + ret = _krb5_socks4a_connect(chgpw_socks4a_io(&sock), + hi->hostname, hi->port, proxy_userid, &socks4a); + if (ret) + continue; + + /* + * Until the SOCKS4a connection has succeeeded, wait + * until we can read or write as needed by SOCKS4a and + * do the SOCKS4a I/O. + */ + while (!_krb5_socks4a_connected(socks4a)) { + fd_set readfds, writefds; + int nready; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + if (_krb5_socks4a_reading(socks4a)) + FD_SET(sock, &readfds); + if (_krb5_socks4a_writing(socks4a)) + FD_SET(sock, &writefds); + nready = select(sock + 1, &readfds, &writefds, + /*exceptfds*/NULL, /*timeout*/NULL); + if (nready == -1) { + ret = errno; + break; + } + if (nready <= 0) { + ret = EIO; /* timeout should be impossible */ + break; + } + ret = _krb5_socks4a_io(socks4a); + if (ret) + break; + } + _krb5_socks4a_free(socks4a); + if (ret) + continue; + } + ret = krb5_auth_con_genaddrs (context, auth_context, sock, KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR); if (ret) { @@ -649,6 +789,10 @@ change_password_loop (krb5_context context, out: krb5_krbhst_free (context, handle); krb5_auth_con_free (context, auth_context); + if (proxy_ai) + freeaddrinfo(proxy_ai); + if (proxy_userid) + free(proxy_userid); if (ret == KRB5_KDC_UNREACH) { krb5_set_error_message(context,