Skip to content

Commit

Permalink
New option [libdefaults] socks4a_proxy.
Browse files Browse the repository at this point in the history
All network traffic to KDC goes through the SOCKS4a proxy if it is
configured.

This is deliberately kept simple -- and is not generalized to SOCKS4
or SOCKS5 or other types of proxies -- so it is easy to audit for
network and DNS leaks.  (SOCKS4 works in IP addresses, and so invites
DNS leaks.  SOCKS5 can be OK, if used judiciously, but takes more
work to implement.)

XXX Need to audit Heimdal for other kinds of traffic too outside
libkrb5.  Subsequent changes on this branch will address other parts
of libkrb5.

XXX Need to combine with heimdal#1155
to plug DNS leaks.

XXX Need to figure out where the socks4a.c code should go.

fix heimdal#1151
  • Loading branch information
Taylor R Campbell committed Jan 5, 2024
1 parent 06968e0 commit 3211010
Show file tree
Hide file tree
Showing 9 changed files with 596 additions and 2 deletions.
2 changes: 2 additions & 0 deletions lib/krb5/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ dist_libkrb5_la_SOURCES = \
sendauth.c \
set_default_realm.c \
sock_principal.c \
socks4a.c \
socks4a.h \
store.c \
store-int.c \
store-int.h \
Expand Down
1 change: 1 addition & 0 deletions lib/krb5/NTMakefile
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ libkrb5_OBJS = \
$(OBJ)\sendauth.obj \
$(OBJ)\set_default_realm.obj \
$(OBJ)\sock_principal.obj \
$(OBJ)\socks4a.obj \
$(OBJ)\sp800-108-kdf.obj \
$(OBJ)\store.obj \
$(OBJ)\store-int.obj \
Expand Down
1 change: 1 addition & 0 deletions lib/krb5/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ init_context_from_config_file(krb5_context context)
INIT_FIELD(context, int, max_retries, 3, "max_retries");

INIT_FIELD(context, string, http_proxy, NULL, "http_proxy");
INIT_FIELD(context, string, socks4a_proxy, NULL, "socks4a_proxy");

ret = krb5_config_get_bool_default(context, NULL, FALSE,
"libdefaults",
Expand Down
10 changes: 10 additions & 0 deletions lib/krb5/krb5.conf.5
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,16 @@ enable this option unconditionally.
.It Li warn_pwexpire = Va time
How soon to warn for expiring password.
Default is seven days.
.It Li socks4a_proxy = Va host Ns Oo Li : Ns Va port Oc
SOCKS4a proxy to use when talking to the KDC.
Default port is 1080.
.Pp
Only TCP service to KDC is allowed in this case, not UDP or HTTP.
.Pp
The KDC hostname is passed to the SOCKS4a proxy verbatim without any
DNS resolution first.
Other DNS resolution, of the proxy address and for any realm mapping or
KDC discovery, may still be done outside the SOCKS4a proxy.
.It Li http_proxy = Va proxy-spec
A HTTP-proxy to use when talking to the KDC via HTTP.
.It Li dns_proxy = Va proxy-spec
Expand Down
3 changes: 3 additions & 0 deletions lib/krb5/krb5_locl.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ typedef void (KRB5_LIB_CALL *krb5_gssic_delete_sec_context)(

#define KRB5_GSS_IC_FLAG_RELEASE_CRED 1

struct socks4a; /* XXX */

#include <krb5-private.h>

#include "heim_threads.h"
Expand Down Expand Up @@ -294,6 +296,7 @@ typedef struct krb5_context_data {
const krb5_cc_ops **cc_ops;
int num_cc_ops;
const char *http_proxy;
const char *socks4a_proxy;
const char *time_fmt;
krb5_boolean log_utc;
const char *default_keytab;
Expand Down
127 changes: 125 additions & 2 deletions lib/krb5/send_to_kdc.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

#include "krb5_locl.h"
#include "send_to_kdc_plugin.h"
#include "socks4a.h"

/**
* @section send_to_kdc Locating and sending packets to the KDC
Expand Down Expand Up @@ -326,11 +327,12 @@ struct host_fun {
};

struct host {
enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state;
enum host_state { CONNECT, CONNECTING, PROXYING, CONNECTED, WAITING_REPLY, DEAD } state;
krb5_krbhst_info *hi;
struct addrinfo *freeai;
struct addrinfo *ai;
rk_socket_t fd;
struct socks4a *socks4a;
const struct host_fun *fun;
unsigned int tries;
time_t timeout;
Expand Down Expand Up @@ -393,6 +395,10 @@ deallocate_host(void *ptr)
struct host *host = ptr;
if (!rk_IS_BAD_SOCKET(host->fd))
rk_closesocket(host->fd);
heim_assert((host->state == PROXYING) == (host->socks4a != NULL),
"inconsistent socks4a proxy state");
_krb5_socks4a_free(host->socks4a);
host->socks4a = NULL; /* paranoia */
krb5_data_free(&host->data);
if (host->freeai)
freeaddrinfo(host->freeai);
Expand Down Expand Up @@ -487,6 +493,43 @@ host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
{
krb5_error_code ret;

/*
* If we have a SOCKS4a proxy configured, we need to request
* proxying between when the underlying socket connection succeeds
* and when we enter the CONNECTED state meaning we're ready to
* send and receive application data.
*/
if (context->socks4a_proxy) {
if (host->state == CONNECTING) {
/*
* The underlying socket connection has just succeeded.
* Attempt to request proxying and enter the intermediate
* PROXYING state.
*/
debug_host(context, 5, host, "socks4a proxying");
host->socks4a = NULL;
ret = _krb5_socks4a_connect(host->fd, host->fd,
host->hi->hostname, host->hi->port, /*userid*/NULL,
&host->socks4a);
if (ret) {
host_dead(context, host, "socks4a proxy failed");
return;
}
host->state = PROXYING;
return;
} else {
debug_host(context, 5, host, "socks4a proxied");
heim_assert(host->state == PROXYING, "bad host_connected state");
/*
* The proxy has accepted our proxying request. We are now
* ready to enter the CONNECTED state as if we had no proxy
* in the way.
*/
_krb5_socks4a_free(host->socks4a);
host->socks4a = NULL;
}
}

host->state = CONNECTED;
/*
* Now prepare data to send to host
Expand Down Expand Up @@ -761,6 +804,43 @@ eval_host_state(krb5_context context,
if (host->state == CONNECTING && writeable)
host_connected(context, ctx, host);

if (host->state == PROXYING) {
/*
* Proxy is still connecting. Do an I/O step to see if we
* can make progress.
*/
debug_host(context, 10, host, "socks4a i/o (reading=%d writing=%d)",
_krb5_socks4a_reading(host->socks4a),
_krb5_socks4a_writing(host->socks4a));
heim_assert(context->socks4a_proxy, "proxying without proxy");
heim_assert(_krb5_socks4a_reading(host->socks4a) ? readable : 1,
"woken for read when not readable");
heim_assert(_krb5_socks4a_writing(host->socks4a) ? writeable : 1,
"woken for read when not readable");
ret = _krb5_socks4a_io(host->socks4a);
if (ret) {
_krb5_socks4a_free(host->socks4a);
host->socks4a = NULL;
host_dead(context, host, "socks4a proxy failed");
return 0; /* no reply yet */
}
if (!_krb5_socks4a_connected(host->socks4a)) {
/*
* Proxy is still connecting. Wait until we can do another
* I/O step.
*/
debug_host(context, 10, host, "socks4a still connecting");
return 0;
}
/*
* Proxy has connected on our behalf, transition to CONNECTED
* and start over since _krb5_socks4a_io has consumed the I/O
* state.
*/
host_connected(context, ctx, host);
return 0;
}

if (readable) {

debug_host(context, 5, host, "reading packet");
Expand Down Expand Up @@ -827,7 +907,44 @@ submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)

gettimeofday(&nrstart, NULL);

if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
if (context->socks4a_proxy) {
char *proxy, *proxyhost, *proxyport;
struct addrinfo hints;

/*
* Refuse anything but TCP connections when we have a SOCKS4a
* proxy configured.
*/
if (hi->proto != KRB5_KRBHST_TCP)
return KRB5_KDC_UNREACH;

/*
* Parse `<host>[:<port>]' into parts.
*/
if ((proxy = strdup(context->socks4a_proxy)) == NULL)
return ENOMEM;
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;

/*
* Resolve the proxy's address.
*/
ret = getaddrinfo(proxyhost, proxyport ? proxyport : "1080", &hints,
&ai);
free(proxy);
if (ret)
return krb5_eai_to_heim_errno(ret, errno);
freeai = ai;

} else if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
char *proxy2 = strdup(context->http_proxy);
char *el, *proxy = proxy2;
struct addrinfo hints;
Expand Down Expand Up @@ -1012,6 +1129,12 @@ wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
FD_SET(h->fd, &wait_ctx->rfds);
FD_SET(h->fd, &wait_ctx->wfds);
break;
case PROXYING:
if (_krb5_socks4a_reading(h->socks4a))
FD_SET(h->fd, &wait_ctx->rfds);
if (_krb5_socks4a_writing(h->socks4a))
FD_SET(h->fd, &wait_ctx->wfds);
break;
default:
debug_host(wait_ctx->context, 5, h, "invalid sendto host state");
heim_abort("invalid sendto host state");
Expand Down

0 comments on commit 3211010

Please sign in to comment.