From f3a59a8bae1c4db4a2b47cc1049236bc339a094f Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 9 Jul 2024 11:38:26 +0200 Subject: [PATCH] Add support for UNIX domain sockets Make the KDC and kadmind listen on UNIX domain sockets if any are listed in kdc_listen, kadmind_listen, or kpasswd_listen. Send KDC and kpasswd requests on UNIX domain sockets if any are listed in the kdc and primary_kdc realm variables. [ghudson@mit.edu: combined several commits; simplified client side by treating UNIX domain socket entries like module-generated addresses; edited commit message] ticket: 9155 --- doc/admin/conf_files/kdc_conf.rst | 62 +++++++++++++++--------------- doc/admin/conf_files/krb5_conf.rst | 17 ++++---- src/include/k5-int.h | 1 + src/kadmin/server/ovsec_kadmd.c | 3 ++ src/kdc/main.c | 3 ++ src/lib/krb5/os/locate_kdc.c | 17 ++++++++ src/lib/krb5/os/os-proto.h | 1 + src/lib/krb5/os/sendto_kdc.c | 25 ++++++------ src/lib/krb5/os/trace.c | 2 + src/tests/t_changepw.py | 22 ++++++++++- src/tests/t_sendto_kdc.py | 17 ++++++++ 11 files changed, 119 insertions(+), 51 deletions(-) diff --git a/doc/admin/conf_files/kdc_conf.rst b/doc/admin/conf_files/kdc_conf.rst index ed07d83b0e..f809f77dd5 100644 --- a/doc/admin/conf_files/kdc_conf.rst +++ b/doc/admin/conf_files/kdc_conf.rst @@ -289,16 +289,16 @@ The following tags may be specified in a [realms] subsection: **kadmind_listen** (Whitespace- or comma-separated list.) Specifies the kadmin RPC listening addresses and/or ports for the :ref:`kadmind(8)` daemon. - Each entry may be an interface address, a port number, or an - address and port number separated by a colon. If the address - contains colons, enclose it in square brackets. If no address is - specified, the wildcard address is used. To disable listening for - kadmin RPC connections, set this relation to the empty string with - ``kadmind_listen = ""``. If kadmind fails to bind to any of the - specified addresses, it will fail to start. The default is to - bind to the wildcard address at the port specified in - **kadmind_port**, or the standard kadmin port (749). New in - release 1.15. + Each entry may be an interface address, a port number, an address + and port number separated by a colon, or a UNIX domain socket + pathname. If the address contains colons, enclose it in square + brackets. If no address is specified, the wildcard address is + used. To disable listening for kadmin RPC connections, set this + relation to the empty string with ``kadmind_listen = ""``. If + kadmind fails to bind to any of the specified addresses, it will + fail to start. The default is to bind to the wildcard address at + the port specified in **kadmind_port**, or the standard kadmin + port (749). New in release 1.15. **kadmind_port** (Port number.) Specifies the port on which the :ref:`kadmind(8)` @@ -314,15 +314,16 @@ The following tags may be specified in a [realms] subsection: **kdc_listen** (Whitespace- or comma-separated list.) Specifies the UDP listening addresses and/or ports for the :ref:`krb5kdc(8)` daemon. - Each entry may be an interface address, a port number, or an - address and port number separated by a colon. If the address - contains colons, enclose it in square brackets. If no address is - specified, the wildcard address is used. If no port is specified, - the standard port (88) is used. To disable listening on UDP, set - this relation to the empty string with ``kdc_listen = ""``. - If the KDC daemon fails to bind to any of the specified addresses, - it will fail to start. The default is to bind to the wildcard - address on the standard port. New in release 1.15. + Each entry may be an interface address, a port number, an address + and port number separated by a colon, or a UNIX domain socket + pathname. If the address contains colons, enclose it in square + brackets. If no address is specified, the wildcard address is + used. If no port is specified, the standard port (88) is used. + To disable listening on UDP, set this relation to the empty string + with ``kdc_listen = ""``. If the KDC daemon fails to bind to any + of the specified addresses, it will fail to start. The default is + to bind to the wildcard address on the standard port. New in + release 1.15. **kdc_ports** (Whitespace- or comma-separated list, deprecated.) Prior to @@ -352,17 +353,18 @@ The following tags may be specified in a [realms] subsection: **kdc_tcp_listen** if that relation is not defined. **kpasswd_listen** - (Comma-separated list.) Specifies the kpasswd listening addresses - and/or ports for the :ref:`kadmind(8)` daemon. Each entry may be - an interface address, a port number, or an address and port number - separated by a colon. If the address contains colons, enclose it - in square brackets. If no address is specified, the wildcard - address is used. To disable listening for kpasswd requests, set - this relation to the empty string with ``kpasswd_listen = ""``. - If kadmind fails to bind to any of the specified addresses, it - will fail to start. The default is to bind to the wildcard - address at the port specified in **kpasswd_port**, or the standard - kpasswd port (464). New in release 1.15. + (Comma-separated list.) Specifies the kpasswd listening + addresses and/or ports for the :ref:`kadmind(8)` daemon. Each + entry may be an interface address, a port number, an address and + port number separated by a colon, or a UNIX domain socket + pathname. If the address contains colons, enclose it in square + brackets. If no address is specified, the wildcard address is + used. To disable listening for kpasswd requests, set this + relation to the empty string with ``kpasswd_listen = ""``. If + kadmind fails to bind to any of the specified addresses, it will + fail to start. The default is to bind to the wildcard address at + the port specified in **kpasswd_port**, or the standard kpasswd + port (464). New in release 1.15. **kpasswd_port** (Port number.) Specifies the port on which the :ref:`kadmind(8)` diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst index 315c2ff424..e80e02ebab 100644 --- a/doc/admin/conf_files/krb5_conf.rst +++ b/doc/admin/conf_files/krb5_conf.rst @@ -530,20 +530,21 @@ following tags may be specified in the realm's subsection: been set to ``FILE:/tmp/my_proxy.pem``. **kdc** - The name or address of a host running a KDC for that realm. An - optional port number, separated from the hostname by a colon, may - be included. If the name or address contains colons (for example, - if it is an IPv6 address), enclose it in square brackets to + The name or address of a host running a KDC for the realm, or a + UNIX domain socket path of a locally running KDC. An optional + port number, separated from the hostname by a colon, may be + included. If the name or address contains colons (for example, if + it is an IPv6 address), enclose it in square brackets to distinguish the colon from a port separator. For your computer to be able to communicate with the KDC for each realm, this tag must be given a value in each realm subsection in the configuration file, or there must be DNS SRV records specifying the KDCs. **kpasswd_server** - Points to the server where all the password changes are performed. - If there is no such entry, DNS will be queried (unless forbidden - by **dns_lookup_kdc**). Finally, port 464 on the **admin_server** - host will be tried. + The location of the password change server for the realm, using + the same syntax as **kdc**. If there is no such entry, DNS will + be queried (unless forbidden by **dns_lookup_kdc**). Finally, + port 464 on the **admin_server** host will be tried. **master_kdc** The name for **primary_kdc** prior to release 1.19. Its value is diff --git a/src/include/k5-int.h b/src/include/k5-int.h index ec9e320e16..cfd2cc9393 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -248,6 +248,7 @@ typedef unsigned char u_char; #define KRB5_CONF_KDC_TCP_LISTEN "kdc_tcp_listen" #define KRB5_CONF_KDC_TCP_LISTEN_BACKLOG "kdc_tcp_listen_backlog" #define KRB5_CONF_KDC_TIMESYNC "kdc_timesync" +#define KRB5_CONF_KDC_UNIXSOCK_LISTEN "kdc_unixsock_listen" #define KRB5_CONF_KEY_STASH_FILE "key_stash_file" #define KRB5_CONF_KPASSWD_LISTEN "kpasswd_listen" #define KRB5_CONF_KPASSWD_PORT "kpasswd_port" diff --git a/src/kadmin/server/ovsec_kadmd.c b/src/kadmin/server/ovsec_kadmd.c index 6e080e197d..234d9e2690 100644 --- a/src/kadmin/server/ovsec_kadmd.c +++ b/src/kadmin/server/ovsec_kadmd.c @@ -157,6 +157,9 @@ setup_loop(kadm5_config_params *params, int proponly, verto_ctx **ctx_out) KADM, KADMVERS, kadm_1); if (ret) return ret; + ret = loop_add_unix_socket(params->kpasswd_listen); + if (ret) + return ret; } #ifndef DISABLE_IPROP if (params->iprop_enabled) { diff --git a/src/kdc/main.c b/src/kdc/main.c index bef8b3ee2c..439565cd51 100644 --- a/src/kdc/main.c +++ b/src/kdc/main.c @@ -967,6 +967,9 @@ int main(int argc, char **argv) for (i = 0; i < shandle.kdc_numrealms; i++) { realm = shandle.kdc_realmlist[i]; retval = loop_add_udp_address(KRB5_DEFAULT_PORT, realm->realm_listen); + if (retval) + goto net_init_error; + retval = loop_add_unix_socket(realm->realm_listen); if (retval) goto net_init_error; retval = loop_add_tcp_address(KRB5_DEFAULT_PORT, diff --git a/src/lib/krb5/os/locate_kdc.c b/src/lib/krb5/os/locate_kdc.c index b5e84ebc5e..d1df04a635 100644 --- a/src/lib/krb5/os/locate_kdc.c +++ b/src/lib/krb5/os/locate_kdc.c @@ -295,6 +295,23 @@ locate_srv_conf_1(krb5_context context, const krb5_data *realm, hostspec = hostlist[i]; Tprintf("entry %d is '%s'\n", i, hostspec); +#ifndef _WIN32 + if (hostspec[0] == '/') { + struct sockaddr_un sun = { 0 }; + + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, hostspec, sizeof(sun.sun_path)) >= + sizeof(sun.sun_path)) { + code = ENAMETOOLONG; + goto cleanup; + } + code = add_addr_to_list(serverlist, UNIXSOCK, AF_UNIX, sizeof(sun), + (struct sockaddr *)&sun); + if (code) + goto cleanup; + continue; + } +#endif parse_uri_if_https(hostspec, &this_transport, &hostspec, &uri_path); default_port = (this_transport == HTTPS) ? 443 : udpport; diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h index a21558d234..a60c777a22 100644 --- a/src/lib/krb5/os/os-proto.h +++ b/src/lib/krb5/os/os-proto.h @@ -43,6 +43,7 @@ typedef enum { TCP, UDP, HTTPS, + UNIXSOCK, } k5_transport; typedef enum { diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c index 4b2906139d..9b51e95ae2 100644 --- a/src/lib/krb5/os/sendto_kdc.c +++ b/src/lib/krb5/os/sendto_kdc.c @@ -385,6 +385,7 @@ socktype_for_transport(k5_transport transport) return SOCK_DGRAM; case TCP: case HTTPS: + case UNIXSOCK: return SOCK_STREAM; default: return 0; @@ -668,7 +669,7 @@ set_transport_message(struct conn_state *state, const krb5_data *realm, if (message == NULL || message->length == 0) return 0; - if (state->addr.transport == TCP) { + if (state->addr.transport == TCP || state->addr.transport == UNIXSOCK) { 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); @@ -713,7 +714,7 @@ add_connection(struct conn_state **conns, k5_transport transport, state->fd = INVALID_SOCKET; state->server_index = server_index; SG_SET(&state->out.sgbuf[1], NULL, 0); - if (transport == TCP) { + if (transport == TCP || transport == UNIXSOCK) { state->service_connect = service_tcp_connect; state->service_write = service_tcp_write; state->service_read = service_tcp_read; @@ -822,16 +823,6 @@ resolve_server(krb5_context context, const krb5_data *realm, return 0; transport = (strategy == UDP_FIRST || strategy == ONLY_UDP) ? UDP : TCP; - if (entry->hostname == NULL) { - /* Added by a module, so transport is either TCP or UDP. */ - ai.ai_socktype = socktype_for_transport(entry->transport); - ai.ai_family = entry->family; - ai.ai_addrlen = entry->addrlen; - ai.ai_addr = (struct sockaddr *)&entry->addr; - defer = (entry->transport != transport); - return add_connection(conns, entry->transport, defer, &ai, ind, realm, - NULL, NULL, entry->uri_path, udpbufp); - } /* If the entry has a specified transport, use it, but possibly defer the * addresses we add based on the strategy. */ @@ -841,6 +832,16 @@ resolve_server(krb5_context context, const krb5_data *realm, (entry->transport == UDP && strategy == UDP_LAST); } + if (entry->hostname == NULL) { + /* The entry contains an address; skip name resolution. */ + ai.ai_socktype = socktype_for_transport(entry->transport); + ai.ai_family = entry->family; + ai.ai_addrlen = entry->addrlen; + ai.ai_addr = ss2sa(&entry->addr); + return add_connection(conns, entry->transport, defer, &ai, ind, realm, + NULL, NULL, entry->uri_path, udpbufp); + } + memset(&hint, 0, sizeof(hint)); hint.ai_family = entry->family; hint.ai_socktype = socktype_for_transport(transport); diff --git a/src/lib/krb5/os/trace.c b/src/lib/krb5/os/trace.c index 89e796f694..89699f7df5 100644 --- a/src/lib/krb5/os/trace.c +++ b/src/lib/krb5/os/trace.c @@ -252,6 +252,8 @@ trace_format(krb5_context context, const char *fmt, va_list ap) k5_buf_add(&buf, "stream"); else if (ra->transport == HTTPS) k5_buf_add(&buf, "https"); + else if (ra->transport == UNIXSOCK) + k5_buf_add(&buf, "UNIX domain socket"); else k5_buf_add_fmt(&buf, "transport%d", ra->transport); diff --git a/src/tests/t_changepw.py b/src/tests/t_changepw.py index bf8e3a9eb7..7e8a663977 100755 --- a/src/tests/t_changepw.py +++ b/src/tests/t_changepw.py @@ -1,6 +1,12 @@ from k5test import * -realm = K5Realm(create_host=False, get_creds=False, start_kadmind=True) +# Also listen on a UNIX domain sockets for kpasswd. +unix_conf = {'realms': {'$realm': { + 'kdc_listen': '$port0, $testdir/krb5.sock', + 'kadmind_listen': '$port1, $testdir/kadmin.sock', + 'kpasswd_listen': '$port2, $testdir/kpasswd.sock'}}} +realm = K5Realm(create_host=False,get_creds=False, kdc_conf=unix_conf) +realm.start_kadmind() realm.prep_kadmin() # Mark a principal as expired and change its password through kinit. @@ -47,4 +53,18 @@ realm.kinit('testprinc', 'pw4') realm.run([kdestroy]) realm.run([kadminl, 'delprinc', 'testprinc']) +mark('password change over UNIX domain socket') + +unix_cli_conf = {'realms': {'$realm': { + 'kdc': '$testdir/krb5.sock', + 'admin_server': '$testdir/kadmin.sock', + 'kpasswd_server': '$testdir/kpasswd.sock'}}} +unix_cli = realm.special_env('unix_cli', False, krb5_conf=unix_cli_conf) + +realm.run([kadminl, 'addprinc', '-pw', 'pw1', 'testprinc']) +msgs = ('Sending TCP request to UNIX domain socket',) +realm.run([kpasswd, 'testprinc'], input='pw1\npw2\npw2\n', env=unix_cli, + expected_trace=msgs) +realm.run([kadminl, 'delprinc', 'testprinc']) + success('Password change tests') diff --git a/src/tests/t_sendto_kdc.py b/src/tests/t_sendto_kdc.py index 8a3f8e62d2..d27467b6ba 100644 --- a/src/tests/t_sendto_kdc.py +++ b/src/tests/t_sendto_kdc.py @@ -25,4 +25,21 @@ realm.kinit(realm.user_princ, 'new', env=fallback, expected_trace=msgs) stop_daemon(replica_kdc) +mark('UNIX domain socket') + +conf_unix = {'realms': {'$realm': {'kdc_listen': '$testdir/krb5.sock', + 'kdc_tcp_listen': ''}}} +unix = realm.special_env('unix', True, kdc_conf=conf_unix) +realm.run([kdb5_util, 'load', dumpfile], env=unix) +realm.stop_kdc() +realm.start_kdc(env=unix) + +conf_unix_cli = {'realms': {'$realm': {'kdc': '$testdir/krb5.sock'}}} +unix_cli = realm.special_env('unix_cli', False, krb5_conf=conf_unix_cli) + +# Do a kinit and check if we send the packet via a UNIX domain socket. +msgs = ('Sending TCP request to UNIX domain socket',) +realm.kinit(realm.user_princ, password('user'), env=unix_cli, + expected_trace=msgs) + success('sendto_kdc') -- 2.47.2