From ghudson at mit.edu Mon Feb 3 18:55:42 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Mon, 3 Feb 2025 18:55:42 -0500 (EST) Subject: krb5 commit: Add support for systemd socket activation Message-ID: <20250203235542.718BC101A79@krbdev.mit.edu> https://github.com/krb5/krb5/commit/11b9a8244f1b3f5226f315e998f4e892b262e46e commit 11b9a8244f1b3f5226f315e998f4e892b262e46e Author: Andreas Schneider Date: Wed Nov 20 18:17:29 2024 +0100 Add support for systemd socket activation If LISTEN_PID and LISTEN_FDS are set in the environment, expect listener sockets to be present at fds starting at 3. If any match a configured listener address and port, use it instead of creating a new socket. [ghudson at mit.edu: combined two commits; changed sa_compare() to sa_equal(); restructured changes to fetch listener sockets into a list once and to match on socket type as well as address; added tests] ticket: 9157 (new) .gitignore | 1 + doc/admin/admin_commands/kadmind.rst | 8 ++ doc/admin/admin_commands/krb5kdc.rst | 7 ++ src/include/socket-utils.h | 35 +++++++++ src/kdc/Makefile.in | 10 ++- src/kdc/deps | 10 +++ src/kdc/t_sockact.c | 121 +++++++++++++++++++++++++++++ src/kdc/t_sockact.py | 43 +++++++++++ src/lib/apputils/net-server.c | 144 ++++++++++++++++++++++++++++++----- 9 files changed, 358 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index a7a217a6f..ae6780c37 100644 --- a/.gitignore +++ b/.gitignore @@ -258,6 +258,7 @@ local.properties /src/kdc/rtest /src/kdc/t_ndr /src/kdc/t_replay +/src/kdc/t_sockact /src/lib/k5sprt32.def diff --git a/doc/admin/admin_commands/kadmind.rst b/doc/admin/admin_commands/kadmind.rst index 7e1482635..bc66890de 100644 --- a/doc/admin/admin_commands/kadmind.rst +++ b/doc/admin/admin_commands/kadmind.rst @@ -121,6 +121,14 @@ ENVIRONMENT See :ref:`kerberos(7)` for a description of Kerberos environment variables. +As of release 1.22, kadmind supports systemd socket activation via the +LISTEN_PID and LISTEN_FDS environment variables. Sockets provided by +the caller must correspond to configured listener addresses (via the +**kadmind_listen** or **kpasswd_listen** variables or equivalents) or +they will be ignored. Any configured listener addresses that do not +correspond to caller-provided sockets will be ignored if socket +activation is used. + SEE ALSO -------- diff --git a/doc/admin/admin_commands/krb5kdc.rst b/doc/admin/admin_commands/krb5kdc.rst index 631a0de84..97fbe5ed7 100644 --- a/doc/admin/admin_commands/krb5kdc.rst +++ b/doc/admin/admin_commands/krb5kdc.rst @@ -106,6 +106,13 @@ ENVIRONMENT See :ref:`kerberos(7)` for a description of Kerberos environment variables. +As of release 1.22, krb5kdc supports systemd socket activation via the +LISTEN_PID and LISTEN_FDS environment variables. Sockets provided by +the caller must correspond to configured listener addresses (via the +**kdc_listen** variable or equivalent) or they will be ignored. Any +configured listener addresses that do not correspond to +caller-provided sockets will be ignored if socket activation is used. + SEE ALSO -------- diff --git a/src/include/socket-utils.h b/src/include/socket-utils.h index 177662c87..02c10ec02 100644 --- a/src/include/socket-utils.h +++ b/src/include/socket-utils.h @@ -43,6 +43,8 @@ #ifndef SOCKET_UTILS_H #define SOCKET_UTILS_H +#include + /* Some useful stuff cross-platform for manipulating socket addresses. We assume at least ipv4 sockaddr_in support. The sockaddr_storage stuff comes from the ipv6 socket api enhancements; socklen_t is @@ -159,4 +161,37 @@ sa_socklen(const struct sockaddr *sa) abort(); } +/* Return true if a and b are the same address (and port if applicable). */ +static inline bool +sa_equal(const struct sockaddr *a, const struct sockaddr *b) +{ + if (a == NULL || b == NULL || a->sa_family != b->sa_family) + return false; + + if (a->sa_family == AF_INET) { + const struct sockaddr_in *x = sa2sin(a); + const struct sockaddr_in *y = sa2sin(b); + + if (x->sin_port != y->sin_port) + return false; + return memcmp(&x->sin_addr, &y->sin_addr, sizeof(x->sin_addr)) == 0; + } else if (a->sa_family == AF_INET6) { + const struct sockaddr_in6 *x = sa2sin6(a); + const struct sockaddr_in6 *y = sa2sin6(b); + + if (x->sin6_port != y->sin6_port) + return false; + return memcmp(&x->sin6_addr, &y->sin6_addr, sizeof(x->sin6_addr)) == 0; +#ifndef _WIN32 + } else if (a->sa_family == AF_UNIX) { + const struct sockaddr_un *x = sa2sun(a); + const struct sockaddr_un *y = sa2sun(b); + + return strcmp(x->sun_path, y->sun_path) == 0; +#endif + } + + return false; +} + #endif /* SOCKET_UTILS_H */ diff --git a/src/kdc/Makefile.in b/src/kdc/Makefile.in index 7199b3472..bf4d9b580 100644 --- a/src/kdc/Makefile.in +++ b/src/kdc/Makefile.in @@ -31,7 +31,8 @@ SRCS= \ EXTRADEPSRCS= \ $(srcdir)/t_ndr.c \ - $(srcdir)/t_replay.c + $(srcdir)/t_replay.c \ + $(srcdir)/t_sockact.c OBJS= \ authind.o \ @@ -78,16 +79,21 @@ T_REPLAY_OBJS=t_replay.o t_replay: $(T_REPLAY_OBJS) replay.o $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o $@ $(T_REPLAY_OBJS) $(CMOCKA_LIBS) $(KRB5_BASE_LIBS) +t_sockact: t_sockact.o $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ t_sockact.o $(KRB5_BASE_LIBS) + check-cmocka: t_replay $(RUN_TEST) ./t_replay > /dev/null -check-pytests: +check-pytests: t_sockact $(RUNPYTEST) $(srcdir)/t_workers.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_emptytgt.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_bigreply.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_sockact.py $(PYTESTFLAGS) install: $(INSTALL_PROGRAM) krb5kdc ${DESTDIR}$(SERVER_BINDIR)/krb5kdc clean: $(RM) krb5kdc rtest.o rtest t_replay.o t_replay t_ndr.o t_ndr + $(RM) t_sockact.o t_sockact diff --git a/src/kdc/deps b/src/kdc/deps index 2d54fa93d..86be4c475 100644 --- a/src/kdc/deps +++ b/src/kdc/deps @@ -427,3 +427,13 @@ $(OUTPRE)t_replay.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ extern.h kdc_util.h realm_data.h replay.c reqstate.h \ t_replay.c +$(OUTPRE)t_sockact.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ + $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \ + $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \ + $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h t_sockact.c diff --git a/src/kdc/t_sockact.c b/src/kdc/t_sockact.c new file mode 100644 index 000000000..cb4a4bc7a --- /dev/null +++ b/src/kdc/t_sockact.c @@ -0,0 +1,121 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* kdc/t_sockact.c - socket activation test harness */ +/* + * Copyright (C) 2025 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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 HOLDER 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. + */ + +/* + * Usage: t_sockact address... -- program args... + * + * This program simulates systemd socket activation by creating one or more + * listener sockets at the specified addresses, setting LISTEN_FDS and + * LISTEN_PID in the environment, and executing the specified command. (The + * real systemd would not execute the program until there is input on one of + * the listener sockets, but we do not need to simulate that, and executing the + * command immediately allow easier integration with k5test.py.) + */ + +#include "k5-int.h" +#include "socket-utils.h" + +static int max_fd; + +static void +create_socket(const struct sockaddr *addr) +{ + int fd, one = 1; + + fd = socket(addr->sa_family, SOCK_STREAM, 0); + if (fd < 0) + abort(); + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0) + abort(); +#ifdef SO_REUSEPORT + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) != 0) + abort(); +#endif +#if defined(IPV6_V6ONLY) + if (addr->sa_family == AF_INET6) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) != 0) + abort(); + } +#endif + if (bind(fd, addr, sa_socklen(addr)) != 0) + abort(); + if (listen(fd, 5) != 0) + abort(); + max_fd = fd; +} + +int +main(int argc, char **argv) +{ + const char *addrstr; + struct sockaddr_storage ss = { 0 }; + struct addrinfo hints = { 0 }, *ai_list = NULL, *ai = NULL; + char *host, nbuf[128]; + int i, port; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "--") == 0) + break; + addrstr = argv[i]; + if (*addrstr == '/') { + ss.ss_family = AF_UNIX; + strlcpy(ss2sun(&ss)->sun_path, addrstr, + sizeof(ss2sun(&ss)->sun_path)); + create_socket(ss2sa(&ss)); + } else { + if (k5_parse_host_string(addrstr, 0, &host, &port) != 0 || !port) + abort(); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_NUMERICSERV + hints.ai_flags |= AI_NUMERICSERV; +#endif + (void)snprintf(nbuf, sizeof(nbuf), "%d", port); + if (getaddrinfo(host, nbuf, &hints, &ai_list) != 0) + abort(); + for (ai = ai_list; ai != NULL; ai = ai->ai_next) + create_socket(ai->ai_addr); + freeaddrinfo(ai_list); + free(host); + } + } + argv += i + 1; + + (void)snprintf(nbuf, sizeof(nbuf), "%d", max_fd - 2); + setenv("LISTEN_FDS", nbuf, 1); + (void)snprintf(nbuf, sizeof(nbuf), "%lu", (unsigned long)getpid()); + setenv("LISTEN_PID", nbuf, 1); + execv(argv[0], argv); + abort(); + return 1; +} diff --git a/src/kdc/t_sockact.py b/src/kdc/t_sockact.py new file mode 100644 index 000000000..2a4aeb9ed --- /dev/null +++ b/src/kdc/t_sockact.py @@ -0,0 +1,43 @@ +from k5test import * + +if not which('systemd-socket-activate'): + skip_rest('socket activation tests', 'systemd-socket-activate not found') + +# Configure listeners for two UNIX domain sockets and two ports. +kdc_conf = {'realms': {'$realm': { + 'kdc_listen': '$testdir/sock1 $testdir/sock2', + 'kdc_tcp_listen': '$port7 $port8'}}} +realm = K5Realm(kdc_conf=kdc_conf, start_kdc=False) + +# Create socket activation fds for just one of the UNIX domain sockets +# and one of the ports. +realm.start_server(['./t_sockact', os.path.join(realm.testdir, 'sock1'), + str(realm.portbase + 8), '--', krb5kdc, '-n'], + 'starting...') + +mark('UNIX socket 1') +cconf1 = {'realms': {'$realm': {'kdc': '$testdir/sock1'}}} +env1 = realm.special_env('sock1', False, krb5_conf=cconf1) +realm.kinit(realm.user_princ, password('user'), env=env1) + +mark('port8') +cconf2 = {'realms': {'$realm': {'kdc': '$hostname:$port8'}}} +env2 = realm.special_env('sock1', False, krb5_conf=cconf2) +realm.kinit(realm.user_princ, password('user'), env=env2) + +# Test that configured listener addresses are ignored if they don't +# match caller-provided sockets. + +mark('UNIX socket 2') +cconf3 = {'realms': {'$realm': {'kdc': '$testdir/sock2'}}} +env3 = realm.special_env('sock2', False, krb5_conf=cconf3) +realm.kinit(realm.user_princ, password('user'), env=env3, expected_code=1, + expected_msg='Cannot contact any KDC') + +mark('port7') +cconf4 = {'realms': {'$realm': {'kdc': '$hostname:$port7'}}} +env4 = realm.special_env('sock1', False, krb5_conf=cconf4) +realm.kinit(realm.user_princ, password('user'), env=env3, expected_code=1, + expected_msg='Cannot contact any KDC') + +success('systemd socket activation tests') diff --git a/src/lib/apputils/net-server.c b/src/lib/apputils/net-server.c index 6fa8a97e0..c8c83606d 100644 --- a/src/lib/apputils/net-server.c +++ b/src/lib/apputils/net-server.c @@ -65,6 +65,19 @@ #include "udppktinfo.h" +/* List of systemd socket activation addresses and socket types. */ +struct sockact_list { + size_t nsockets; + struct { + struct sockaddr_storage addr; + int type; + } *fds; +}; + +/* When systemd socket activation is used, caller-provided sockets begin at + * file descriptor 3. */ +const int SOCKACT_START = 3; + /* XXX */ #define KDC5_NONET (-1779992062L) @@ -694,6 +707,82 @@ static const enum conn_type bind_conn_types[] = [UNX] = CONN_UNIXSOCK_LISTENER }; +/* If any systemd socket activation fds are indicated by the environment, set + * them close-on-exec and put their addresses and socket types into *list. */ +static void +init_sockact_list(struct sockact_list *list) +{ + const char *v; + char *end; + long lpid; + int fd; + size_t nfds, i; + socklen_t slen; + + list->nsockets = 0; + list->fds = NULL; + + /* Check if LISTEN_FDS is meant for this process. */ + v = getenv("LISTEN_PID"); + if (v == NULL) + return; + lpid = strtol(v, &end, 10); + if (end == NULL || end == v || *end != '\0' || lpid != getpid()) + return; + + /* Get the number of activated sockets. */ + v = getenv("LISTEN_FDS"); + if (v == NULL) + return; + nfds = strtoul(v, &end, 10); + if (end == NULL || end == v || *end != '\0') + return; + if (nfds == 0 || nfds > (size_t)INT_MAX - SOCKACT_START) + return; + + list->fds = calloc(nfds, sizeof(*list->fds)); + if (list->fds == NULL) + return; + + for (i = 0; i < nfds; i++) { + fd = i + SOCKACT_START; + set_cloexec_fd(fd); + slen = sizeof(list->fds[i].addr); + (void)getsockname(fd, ss2sa(&list->fds[i].addr), &slen); + slen = sizeof(list->fds[i].type); + (void)getsockopt(fd, SOL_SOCKET, SO_TYPE, &list->fds[i].type, &slen); + } + + list->nsockets = nfds; +} + +/* Release any storage used by *list. */ +static void +fini_sockact_list(struct sockact_list *list) +{ + free(list->fds); + list->fds = NULL; + list->nsockets = 0; +} + +/* If sa matches an address in *list, return the associated file descriptor and + * clear the address from *list. Otherwise return -1. */ +static int +find_sockact(struct sockact_list *list, const struct sockaddr *sa, int type) +{ + size_t i; + + for (i = 0; i < list->nsockets; i++) { + if (list->fds[i].type == type && + sa_equal(ss2sa(&list->fds[i].addr), sa)) { + list->fds[i].type = -1; + memset(&list->fds[i].addr, 0, sizeof(list->fds[i].addr)); + return i + SOCKACT_START; + } + } + return -1; +} + /* * Set up a listening socket. * @@ -708,8 +797,9 @@ static const enum conn_type bind_conn_types[] = */ static krb5_error_code setup_socket(struct bind_address *ba, struct sockaddr *sock_address, - void *handle, const char *prog, verto_ctx *ctx, - int listen_backlog, verto_callback vcb, enum conn_type ctype) + struct sockact_list *sockacts, void *handle, const char *prog, + verto_ctx *ctx, int listen_backlog, verto_callback vcb, + enum conn_type ctype) { krb5_error_code ret; struct connection *conn; @@ -722,20 +812,31 @@ setup_socket(struct bind_address *ba, struct sockaddr *sock_address, krb5_klog_syslog(LOG_DEBUG, _("Setting up %s socket for address %s"), bind_type_names[ba->type], addrbuf); - /* Create the socket. */ - ret = create_server_socket(sock_address, bind_socktypes[ba->type], prog, - &sock); - if (ret) - goto cleanup; + if (sockacts->nsockets > 0) { + /* Look for a systemd socket activation fd matching sock_address. */ + sock = find_sockact(sockacts, sock_address, bind_socktypes[ba->type]); + if (sock == -1) { + /* Ignore configured addresses that don't match any caller-provided + * sockets. */ + ret = 0; + goto cleanup; + } + } else { + /* We're not using socket activation; create the socket. */ + ret = create_server_socket(sock_address, bind_socktypes[ba->type], + prog, &sock); + if (ret) + goto cleanup; - /* Listen for backlogged connections on stream sockets. (For RPC sockets - * this will be done by svc_register().) */ - if ((ba->type == TCP || ba->type == UNX) && - listen(sock, listen_backlog) != 0) { - ret = errno; - com_err(prog, errno, _("Cannot listen on %s server socket on %s"), - bind_type_names[ba->type], addrbuf); - goto cleanup; + /* Listen for backlogged connections on stream sockets. (For RPC + * sockets this will be done by svc_register().) */ + if ((ba->type == TCP || ba->type == UNX) && + listen(sock, listen_backlog) != 0) { + ret = errno; + com_err(prog, errno, _("Cannot listen on %s server socket on %s"), + bind_type_names[ba->type], addrbuf); + goto cleanup; + } } /* Set non-blocking I/O for non-RPC listener sockets. */ @@ -837,6 +938,7 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog, struct bind_address addr; struct sockaddr_un sun; struct addrinfo hints, *ai_list = NULL, *ai = NULL; + struct sockact_list sockacts = { 0 }; verto_callback vcb; char addrbuf[128]; @@ -855,6 +957,8 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog, hints.ai_flags |= AI_NUMERICSERV; #endif + init_sockact_list(&sockacts); + /* Add all the requested addresses. */ for (i = 0; i < bind_addresses.n; i++) { addr = bind_addresses.data[i]; @@ -870,8 +974,9 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog, addr.address); goto cleanup; } - ret = setup_socket(&addr, (struct sockaddr *)&sun, handle, prog, - ctx, listen_backlog, verto_callbacks[addr.type], + ret = setup_socket(&addr, (struct sockaddr *)&sun, &sockacts, + handle, prog, ctx, listen_backlog, + verto_callbacks[addr.type], bind_conn_types[addr.type]); if (ret) { krb5_klog_syslog(LOG_ERR, @@ -911,8 +1016,8 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog, /* Set the real port number. */ sa_setport(ai->ai_addr, addr.port); - ret = setup_socket(&addr, ai->ai_addr, handle, prog, ctx, - listen_backlog, verto_callbacks[addr.type], + ret = setup_socket(&addr, ai->ai_addr, &sockacts, handle, prog, + ctx, listen_backlog, verto_callbacks[addr.type], bind_conn_types[addr.type]); if (ret) { k5_print_addr(ai->ai_addr, addrbuf, sizeof(addrbuf)); @@ -937,6 +1042,7 @@ setup_addresses(verto_ctx *ctx, void *handle, const char *prog, cleanup: if (ai_list != NULL) freeaddrinfo(ai_list); + fini_sockact_list(&sockacts); return ret; } From ghudson at mit.edu Thu Feb 6 18:02:18 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Thu, 6 Feb 2025 18:02:18 -0500 (EST) Subject: krb5 commit: Allow only one salt type per enctype in key data Message-ID: <20250206230218.D1684101CA3@krbdev.mit.edu> https://github.com/krb5/krb5/commit/367ccd2fcf8b4adb82bfc7ef9b5f04ff94f80326 commit 367ccd2fcf8b4adb82bfc7ef9b5f04ff94f80326 Author: Greg Hudson Date: Wed Jan 29 00:22:57 2025 -0500 Allow only one salt type per enctype in key data In the default libkdb5 password change method, omit requested key/salt combinations that duplicate an earlier encryption type, even if they have a different salt type. Any use cases for multiple salts for the same enctype disappeared with single-DES support. (We already have this behavior for chrand requests.) ticket: 9160 (new) src/lib/kdb/kdb_cpw.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/kdb/kdb_cpw.c b/src/lib/kdb/kdb_cpw.c index c33c7cf8d..8b012e19e 100644 --- a/src/lib/kdb/kdb_cpw.c +++ b/src/lib/kdb/kdb_cpw.c @@ -264,8 +264,7 @@ add_key_pwd(krb5_context context, krb5_keyblock *master_key, &similar))) return(retval); - if (similar && - (ks_tuple[j].ks_salttype == ks_tuple[i].ks_salttype)) + if (similar) break; } From ghudson at mit.edu Thu Feb 6 18:43:34 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Thu, 6 Feb 2025 18:43:34 -0500 (EST) Subject: krb5 commit: Improve ulog block resize efficiency Message-ID: <20250206234334.EA2D3101CA3@krbdev.mit.edu> https://github.com/krb5/krb5/commit/197ddd4196a3049cd17d29d1c8f0af1424472956 commit 197ddd4196a3049cd17d29d1c8f0af1424472956 Author: Zoltan Borbely Date: Fri Jan 31 18:00:17 2025 +0100 Improve ulog block resize efficiency When it is necessary to increase the ulog block size, copy the existing entries instead of reinitializing the log. [ghudson at mit.edu: added test case; renamed and split INDEX() to avoid duplication; added sync_ulog() helper; modified copying loop for clarity; edited commit message] ticket: 9161 (new) src/include/kdb_log.h | 20 ++++++++++----- src/kprop/kproplog.c | 2 +- src/lib/kdb/kdb_log.c | 68 +++++++++++++++++++++++++++++++++++---------------- src/tests/t_iprop.py | 30 +++++++++++++++++++++-- 4 files changed, 90 insertions(+), 30 deletions(-) diff --git a/src/include/kdb_log.h b/src/include/kdb_log.h index 423957565..2856859f8 100644 --- a/src/include/kdb_log.h +++ b/src/include/kdb_log.h @@ -18,12 +18,6 @@ extern "C" { #endif -/* - * DB macros - */ -#define INDEX(ulog, i) (kdb_ent_header_t *)(void *) \ - ((char *)(ulog) + sizeof(kdb_hlog_t) + (i) * ulog->kdb_block) - /* * Current DB version # */ @@ -106,6 +100,20 @@ typedef struct _kdb_log_context { int ulogfd; } kdb_log_context; +/* Return the address of the i'th record in ulog for the given block size. */ +static inline uint8_t * +ulog_record_ptr(kdb_hlog_t *ulog, size_t i, size_t bsize) +{ + return (uint8_t *)ulog + sizeof(*ulog) + i * bsize; +} + +/* Return the i'th update entry header for ulog. */ +static inline kdb_ent_header_t * +ulog_index(kdb_hlog_t *ulog, size_t i) +{ + return (void *)ulog_record_ptr(ulog, i, ulog->kdb_block); +} + #ifdef __cplusplus } #endif diff --git a/src/kprop/kproplog.c b/src/kprop/kproplog.c index 1f10aa6dc..45ded429e 100644 --- a/src/kprop/kproplog.c +++ b/src/kprop/kproplog.c @@ -330,7 +330,7 @@ print_update(kdb_hlog_t *ulog, uint32_t entry, uint32_t ulogentries, for (i = start_sno; i < ulog->kdb_last_sno; i++) { indx = i % ulogentries; - indx_log = INDEX(ulog, indx); + indx_log = ulog_index(ulog, indx); /* * Check for corrupt update entry diff --git a/src/lib/kdb/kdb_log.c b/src/lib/kdb/kdb_log.c index 68fae919a..b840eec9a 100644 --- a/src/lib/kdb/kdb_log.c +++ b/src/lib/kdb/kdb_log.c @@ -103,13 +103,31 @@ sync_header(kdb_hlog_t *ulog) } } +/* Sync memory to disk for the entire ulog. */ +static void +sync_ulog(kdb_hlog_t *ulog, uint32_t ulogentries) +{ + size_t len; + + if (!pagesize) + pagesize = getpagesize(); + + len = (sizeof(kdb_hlog_t) + ulogentries * ulog->kdb_block + + (pagesize - 1)) & ~(pagesize - 1); + if (msync(ulog, len, MS_SYNC)) { + /* Couldn't sync to disk, let's panic. */ + syslog(LOG_ERR, _("could not sync the whole ulog to disk")); + abort(); + } +} + /* Return true if the ulog entry for sno matches sno and timestamp. */ static krb5_boolean check_sno(kdb_log_context *log_ctx, kdb_sno_t sno, const kdbe_time_t *timestamp) { unsigned int indx = (sno - 1) % log_ctx->ulogentries; - kdb_ent_header_t *ent = INDEX(log_ctx->ulog, indx); + kdb_ent_header_t *ent = ulog_index(log_ctx->ulog, indx); return ent->kdb_entry_sno == sno && time_equal(&ent->kdb_time, timestamp); } @@ -175,17 +193,16 @@ extend_file_to(int fd, unsigned int new_size) return 0; } -/* - * Resize the array elements. We reinitialize the update log rather than - * unrolling the the log and copying it over to a temporary log for obvious - * performance reasons. Replicas will subsequently do a full resync, but the - * need for resizing should be very small. - */ +/* Resize the array elements of ulog to be at least as large as recsize. Move + * the existing elements into the proper offsets for the new block size. */ static krb5_error_code resize(kdb_hlog_t *ulog, uint32_t ulogentries, int ulogfd, unsigned int recsize, const kdb_incr_update_t *upd) { - unsigned int new_block, new_size; + size_t old_block = ulog->kdb_block, new_block, new_size; + krb5_error_code retval; + uint8_t *old_ent, *new_ent; + uint32_t i; if (ulog == NULL) return KRB5_LOG_ERROR; @@ -204,16 +221,25 @@ resize(kdb_hlog_t *ulog, uint32_t ulogentries, int ulogfd, if (new_size > MAXLOGLEN) return KRB5_LOG_ERROR; - /* Reinit log with new block size. */ - memset(ulog, 0, sizeof(*ulog)); - ulog->kdb_hmagic = KDB_ULOG_HDR_MAGIC; - ulog->db_version_num = KDB_VERSION; - ulog->kdb_state = KDB_STABLE; - ulog->kdb_block = new_block; - sync_header(ulog); - /* Expand log considering new block size. */ - return extend_file_to(ulogfd, new_size); + retval = extend_file_to(ulogfd, new_size); + if (retval) + return retval; + + /* Copy each record into its new location and zero out the unused areas. + * The area is overlapping, so we have to iterate backwards. */ + for (i = ulogentries; i > 0; i--) { + old_ent = ulog_record_ptr(ulog, i - 1, old_block); + new_ent = ulog_record_ptr(ulog, i - 1, new_block); + memmove(new_ent, old_ent, old_block); + memset(new_ent + old_block, 0, new_block - old_block); + } + + syslog(LOG_INFO, _("ulog block size has been resized from %lu to %lu"), + (unsigned long)old_block, (unsigned long)new_block); + ulog->kdb_block = new_block; + sync_ulog(ulog, ulogentries); + return 0; } /* Set the ulog to contain only a dummy entry with the given serial number and @@ -222,7 +248,7 @@ static void set_dummy(kdb_log_context *log_ctx, kdb_sno_t sno, const kdbe_time_t *kdb_time) { kdb_hlog_t *ulog = log_ctx->ulog; - kdb_ent_header_t *ent = INDEX(ulog, (sno - 1) % log_ctx->ulogentries); + kdb_ent_header_t *ent = ulog_index(ulog, (sno - 1) % log_ctx->ulogentries); memset(ent, 0, sizeof(*ent)); ent->kdb_umagic = KDB_ULOG_MAGIC; @@ -305,7 +331,7 @@ store_update(kdb_log_context *log_ctx, kdb_incr_update_t *upd) ulog->kdb_state = KDB_UNSTABLE; i = (upd->kdb_entry_sno - 1) % ulogentries; - indx_log = INDEX(ulog, i); + indx_log = ulog_index(ulog, i); memset(indx_log, 0, ulog->kdb_block); indx_log->kdb_umagic = KDB_ULOG_MAGIC; @@ -335,7 +361,7 @@ store_update(kdb_log_context *log_ctx, kdb_incr_update_t *upd) } else { /* We are circling; set kdb_first_sno and time to the next update. */ i = upd->kdb_entry_sno % ulogentries; - indx_log = INDEX(ulog, i); + indx_log = ulog_index(ulog, i); ulog->kdb_first_sno = indx_log->kdb_entry_sno; ulog->kdb_first_time = indx_log->kdb_time; } @@ -593,7 +619,7 @@ ulog_get_entries(krb5_context context, const kdb_last_t *last, for (; sno < ulog->kdb_last_sno; sno++) { indx = sno % ulogentries; - indx_log = INDEX(ulog, indx); + indx_log = ulog_index(ulog, indx); memset(upd, 0, sizeof(kdb_incr_update_t)); xdrmem_create(&xdrs, (char *)indx_log->entry_data, diff --git a/src/tests/t_iprop.py b/src/tests/t_iprop.py index b356971db..1f1634f31 100755 --- a/src/tests/t_iprop.py +++ b/src/tests/t_iprop.py @@ -86,8 +86,10 @@ def wait_for_prop(kpropd, full_expected, expected_old, expected_new): # Verify the output of kproplog against the expected number of # entries, first and last serial number, and a list of principal names # for the update entrires. -def check_ulog(num, first, last, entries, env=None): +def check_ulog(num, first, last, entries, env=None, bsize=2048): out = realm.run([kproplog], env=env) + if 'Entry block size : ' + str(bsize) + '\n' not in out: + fail('Expected block size %d' % bsize) if 'Number of entries : ' + str(num) + '\n' not in out: fail('Expected %d entries' % num) if last: @@ -458,8 +460,32 @@ for realm in multidb_realms(kdc_conf=conf, create_user=False, wait_for_prop(kpropd2, True, 6, 1) check_ulog(1, 1, 1, [None], replica2) - # Stop the kprop daemons so we can test kpropd -t. + # Create an update large enough to cause a block resize, and make + # sure that it propagates incrementally. + mark('block resize') + cmd = [kadminl, 'cpw', + '-e', 'aes128-sha1,aes256-sha1,aes128-sha2,aes256-sha2', + '-randkey', '-keepold', pr2] + n = 6 + for i in range(n): + realm.run(cmd) + check_ulog(n + 1, 1, n + 1, [None] + n * [pr2], bsize=4096) + kpropd1.send_signal(signal.SIGUSR1) + wait_for_prop(kpropd1, False, 1, n + 1) + check_ulog(n + 1, 1, n + 1, [None] + n * [pr2], replica1, bsize=4096) + kpropd2.send_signal(signal.SIGUSR1) + wait_for_prop(kpropd2, False, 1, n + 1) + check_ulog(n + 1, 1, n + 1, [None] + n * [pr2], replica2, bsize=4096) + + # Reset the ulog again. + realm.run([kproplog, '-R']) + kpropd1.send_signal(signal.SIGUSR1) + wait_for_prop(kpropd1, True, 7, 1) + kpropd2.send_signal(signal.SIGUSR1) + wait_for_prop(kpropd2, True, 7, 1) realm.stop_kpropd(kpropd1) + + # Stop the kprop daemons so we can test kpropd -t. stop_daemon(kpropd2) stop_daemon(kadmind_proponly) mark('kpropd -t') From ghudson at mit.edu Thu Feb 20 22:17:49 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Thu, 20 Feb 2025 22:17:49 -0500 (EST) Subject: krb5 commit: Remove 32-bit build from KfW installer Message-ID: <20250221031749.932AD101CC2@krbdev.mit.edu> https://github.com/krb5/krb5/commit/5f33fb2cf19562175c8271cb9c54a67212f63b93 commit 5f33fb2cf19562175c8271cb9c54a67212f63b93 Author: Greg Hudson Date: Wed Dec 18 14:52:28 2024 -0500 Remove 32-bit build from KfW installer In src/windows/README, document only the steps for a 64-bit build. In the installer, expect only 64-bit binaries. In the CI, perform only a 64-bit Windows build, and split up the build into three steps so that build failures are easier to understand. .github/workflows/build.yml | 33 +-- src/windows/README | 48 ++-- src/windows/installer/wix/config.wxi | 1 - src/windows/installer/wix/custom/custom.cpp | 1 - src/windows/installer/wix/features.wxi | 34 +-- src/windows/installer/wix/files.wxi | 199 +++++------------ src/windows/installer/wix/kfw.wxs | 6 +- src/windows/installer/wix/lang/config_1033.wxi | 11 +- src/windows/installer/wix/lang/strings_1033.wxl | 6 +- src/windows/installer/wix/msi-deployment-guide.txt | 5 - src/windows/installer/wix/platform.wxi | 247 +++++++-------------- src/windows/installer/wix/runtime.wxi | 11 +- 12 files changed, 167 insertions(+), 435 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d7d02e74..183b7c293 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,32 +79,19 @@ jobs: mkdir %KRB_INSTALL_DIR% - uses: ilammy/msvc-dev-cmd at v1 with: - arch: x86 - - name: Build 32-bit - shell: cmd + arch: x64 + - name: Build + working-directory: src run: | - cd src - set - set PATH=%PATH%;%wix%bin + $env:Path += ';' + $env:WindowsSdkVerBinPath + '\\x86' nmake -f Makefile.in prep-windows nmake - nmake install - cd windows\installer\wix - nmake - rename kfw.msi kfw32.msi - - uses: ilammy/msvc-dev-cmd at v1 - with: - arch: x64 - - name: Build 64-bit - shell: cmd + - name: Populate install dir + working-directory: src run: | - cd src - set - set PATH=%PATH%;%wix%bin;"%WindowsSdkVerBinPath%"\x86 - nmake clean - nmake nmake install - cd windows\installer\wix - nmake clean + - name: Build installer + working-directory: src\windows\installer\wix + run: | + $env:Path += ';' + $env:wix + 'bin' nmake - rename kfw.msi kfw64.msi diff --git a/src/windows/README b/src/windows/README index 2d57f0dd1..5ceff9adc 100644 --- a/src/windows/README +++ b/src/windows/README @@ -51,12 +51,9 @@ checking out the sources with git and are using the Git BASH Perl, make sure to set git's core.autocrlf variable to "input" or "false" to avoid translating newlines. -After Visual Studio is installed, you should be able to invoke 32-bit -and 64-bit command prompts via the start menu (Visual Studio 2017 -> -x86 Native Tools Command Prompt and x64 Native Tools Command Prompt). -At the current time, Kerberos 5 can only be built for the x64 target -if the host platform is also 64-bit, because it compiles and runs -programs during the build. +After Visual Studio is installed, you should be able to invoke command +prompts via the start menu (Visual Studio 2017 -> x64 Native Tools +Command Prompt). IMPORTANT NOTE: By default, the sources are built with debug information and linked against the debug version of the Microsoft C @@ -76,44 +73,25 @@ You must also define KRB_INSTALL_DIR either in the environment or on the command line (for nmake install). If you are proceeding to build the MSI installer, this directory should be a temporary staging area in or near your build tree. The directory must exist before nmake install -is run. The 64-bit installer provides 32-bit libraries, so a 32-bit build -and install must be performed before the 64-bit build. +is run. To skip building the graphical ticket manager, run "set NO_LEASH=1" -before building, and do not build the installers. +before building, and do not build the installer. -In a 32-bit command shell: +Run the following commands in a Visual Studio command prompt: - 1) set KRB_INSTALL_DIR=\path\to\dir # Where bin/include/lib lives - 2) cd xxx\src # Go to where source lives - 3) nmake -f Makefile.in prep-windows # Create Makefile for Windows + 1) set PATH=%PATH%;"%WindowsSdkVerBinPath%"\x86 # To get uicc.exe + 2) set KRB_INSTALL_DIR=\path\to\dir # Where bin/include/lib lives + 3) cd xxx\src # Go to where source lives 4) nmake [NODEBUG=1] # Build the sources - 5) nmake install [NODEBUG=1] # Copy headers, libs, executables - 6) cd windows\installer\wix # Go to where the installer source is + 5) nmake install [NODEBUG=1] # Copy libraries/executables + 6) cd windows\installer\wix # Go to the installer source 7) nmake [NODEBUG=1] # Build the installer - 8) rename kfw.msi kfw32.msi # Save the 32-bit installer - -In a 64-bit command shell: - - 9) set PATH=%PATH%;"%WindowsSdkVerBinPath%"\x86 # To get uicc.exe -10) set KRB_INSTALL_DIR=\path\to\dir # Where bin/include/lib lives -11) cd xxx\src # Go to where source lives -12) nmake clean # Clean up the 32-bit objects -13) nmake [NODEBUG=1] # Build the sources for 64-bit -14) nmake install [NODEBUG=1] # Copy 64-bit lib/executables -15) cd windows\installer\wix # Back to the installer source -16) nmake clean # Remove 32-bit leavings -17) nmake [NODEBUG=1] # Build the 64-bit installer -18) rename kfw.msi kfw64.msi # And name it usefully - -Step 9 may be skipped if uicc is already in the command-line path (try + +Step 1 may be skipped if uicc is already in the command-line path (try running "uicc" to see if you get a usage message or a not-found error), or if you are not building the graphical ticket manager. -Visual Studio 2013 and 2015 provide only a single command prompt. -Within this prompt, use "vcvarsall.bat x86" and "vcvarsall.bat amd64" -to switch to 32-bit and 64-bit mode. - Running Kerberos 5 Apps: ----------------------- diff --git a/src/windows/installer/wix/config.wxi b/src/windows/installer/wix/config.wxi index 579de4399..ea6e610a0 100644 --- a/src/windows/installer/wix/config.wxi +++ b/src/windows/installer/wix/config.wxi @@ -58,7 +58,6 @@ - diff --git a/src/windows/installer/wix/custom/custom.cpp b/src/windows/installer/wix/custom/custom.cpp index 3460def69..27e056fb0 100644 --- a/src/windows/installer/wix/custom/custom.cpp +++ b/src/windows/installer/wix/custom/custom.cpp @@ -541,7 +541,6 @@ _cleanup: static bool IsNSISInstalled() { HKEY nsisKfwKey = NULL; - // Note: check Wow6432 node if 64 bit build HRESULT res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion" "\\Uninstall\\Kerberos for Windows", diff --git a/src/windows/installer/wix/features.wxi b/src/windows/installer/wix/features.wxi index 5b0747a6a..3405dc482 100644 --- a/src/windows/installer/wix/features.wxi +++ b/src/windows/installer/wix/features.wxi @@ -56,21 +56,16 @@ - - - - - - - - - - - - + + + + + + + + - @@ -79,19 +74,10 @@ - - - - - - - - - @@ -145,11 +131,7 @@ Level="130" Title="!(loc.KerberosSDKTitle)"> - - - - diff --git a/src/windows/installer/wix/files.wxi b/src/windows/installer/wix/files.wxi index 947bed565..805856eae 100644 --- a/src/windows/installer/wix/files.wxi +++ b/src/windows/installer/wix/files.wxi @@ -80,9 +80,6 @@ KRB5PRESERVEIDENTITY - - - @@ -93,9 +90,6 @@ - - - @@ -136,29 +130,6 @@ - - - - - - kerberos.mit.edu - kerberos-1.mit.edu - kerberos-2.mit.edu - - - - - kerberos-1.csail.mit.edu - kerberos-2.csail.mit.edu - - - - - - - - - @@ -194,10 +165,6 @@ - - - - @@ -270,53 +237,52 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + kerberos.mit.edu + kerberos-1.mit.edu + kerberos-2.mit.edu + + + + + kerberos-1.csail.mit.edu + kerberos-2.csail.mit.edu + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + @@ -336,21 +302,12 @@ - - + + - - - - - - - - - - + @@ -401,19 +358,6 @@ - - - - - - - - - - - - - @@ -424,7 +368,6 @@ - @@ -460,20 +403,6 @@ - - - - - - - - - - - - - - @@ -554,38 +483,16 @@ - - - - - + - - - - - - - - - - - - - - - - + + + + - - - - - - - - - + + + diff --git a/src/windows/installer/wix/kfw.wxs b/src/windows/installer/wix/kfw.wxs index d150a61fd..1f1417dd9 100755 --- a/src/windows/installer/wix/kfw.wxs +++ b/src/windows/installer/wix/kfw.wxs @@ -64,11 +64,7 @@ (Not (VersionNT = 600)) Or (ServicePackLevel >= 2) USELEASH Or USENETIDMGR Not (USELEASH And USENETIDMGR) - - - - - + diff --git a/src/windows/installer/wix/lang/config_1033.wxi b/src/windows/installer/wix/lang/config_1033.wxi index 3dbaaf54b..2f1f4ab51 100644 --- a/src/windows/installer/wix/lang/config_1033.wxi +++ b/src/windows/installer/wix/lang/config_1033.wxi @@ -27,15 +27,8 @@ - - - - - - - - - + + diff --git a/src/windows/installer/wix/lang/strings_1033.wxl b/src/windows/installer/wix/lang/strings_1033.wxl index 7207e9a8d..e38da59bb 100644 --- a/src/windows/installer/wix/lang/strings_1033.wxl +++ b/src/windows/installer/wix/lang/strings_1033.wxl @@ -25,10 +25,8 @@ or implied warranty. --> - Kerberos for Windows (64-bit) - KFW64 - Kerberos for Windows (32-bit) - KFW32 + Kerberos for Windows (64-bit) + KFW64 MIT Debug/Checked Beta diff --git a/src/windows/installer/wix/msi-deployment-guide.txt b/src/windows/installer/wix/msi-deployment-guide.txt index 2d3bb866e..73cb93873 100644 --- a/src/windows/installer/wix/msi-deployment-guide.txt +++ b/src/windows/installer/wix/msi-deployment-guide.txt @@ -825,11 +825,6 @@ Kerberos for Windows means that group policy based deployments will fail on machines that have the "Kerberos for Windows" NSIS package installed. - Note that the NSIS package is only available for 32-bit i386. - You cannot install both the 32-bit NSIS and 64-bit amd64 MSI - packages on the same machine. To install both 32-bit and 64-bit - KFW, you must use the MSI packages of both. - If you have used a different MSI package to install Kerberos for Windows and wish to upgrade it you can author rows into the 'Upgrade' table to have the "Kerberos for Windows" MSI replace these diff --git a/src/windows/installer/wix/platform.wxi b/src/windows/installer/wix/platform.wxi index 8d21fd233..006e35544 100644 --- a/src/windows/installer/wix/platform.wxi +++ b/src/windows/installer/wix/platform.wxi @@ -1,175 +1,82 @@ - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/windows/installer/wix/runtime.wxi b/src/windows/installer/wix/runtime.wxi index 3d5c1dfea..48cf2e131 100644 --- a/src/windows/installer/wix/runtime.wxi +++ b/src/windows/installer/wix/runtime.wxi @@ -1,17 +1,8 @@ -??? + - - - - - - - - - From ghudson at mit.edu Thu Feb 27 13:17:40 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Thu, 27 Feb 2025 13:17:40 -0500 (EST) Subject: krb5 commit: Add alias support Message-ID: <20250227181740.52587101CC4@krbdev.mit.edu> https://github.com/krb5/krb5/commit/5d3fe31bf1dc48e8ee946bf65428611958cac329 commit 5d3fe31bf1dc48e8ee946bf65428611958cac329 Author: Greg Hudson Date: Tue Jan 14 21:25:51 2025 -0500 Add alias support Add a new kadmin command add_alias. Implement it for DB2 and LMDB by writing stub principal entries with a tl-data entry giving the target name. Add libkdb5 functions to create and interpret alias entries. Handle these stub entries in krb5_db_get_principal(), iteratively resolving aliases up to a depth of 10. To allow kadm5_delete_principal() to work on aliases, remove the code that fetches the entry prior to deletion; it was needed before commit 0780e46fc13dbafa177525164997cd204cc50b51 to decrement the policy reference count, but now serves no purpose. Adjust kdb_delete_entry() to translate KRB5_KDB_NOENTRY instead of ignoring it, as we still want to return KADM5_UNK_PRINC when deleting a nonexistent principal name. Modify the LDAP KDB module to work with alias entries. In krb5_ldap_put_principal(), recognize stub alias entries and add an alias to the object for the target principal. In krb5_ldap_delete_principal(), don't delete the LDAP object when deleting an alias name. In krb5_ldap_iterate(), generate stub entries for each alias name in addition to the populated entry for the canonical name. A small amount of refactoring was done as part of this work: the LDAP-specific principal name parsing and unparsing functions were simplified, and a helper function search_princ() was added to find the LDAP object for a principal name. In kdb5_util tabdump, add a dump type "alias" to display a list of aliases in the database. Based on work by Alexander Bokovoy. doc/admin/admin_commands/kadmin_local.rst | 22 +- doc/admin/admin_commands/kdb5_util.rst | 8 + doc/admin/conf_ldap.rst | 7 +- doc/admin/database.rst | 4 + src/include/kdb.h | 28 +- src/include/krb5/kadm5_auth_plugin.h | 11 +- src/include/krb5/kadm5_hook_plugin.h | 8 +- src/kadmin/cli/kadmin.c | 49 +++ src/kadmin/cli/kadmin.h | 2 + src/kadmin/cli/kadmin_ct.ct | 3 + src/kadmin/dbutil/tabdump.c | 38 ++ src/kadmin/server/auth.c | 8 +- src/kadmin/server/auth.h | 2 + src/kadmin/server/auth_acl.c | 14 + src/kadmin/server/kadm_rpc_svc.c | 7 + src/kadmin/server/server_stubs.c | 259 ++++++++----- src/lib/kadm5/admin.h | 3 + src/lib/kadm5/admin_xdr.h | 1 + src/lib/kadm5/clnt/client_principal.c | 20 + src/lib/kadm5/clnt/client_rpc.c | 8 + src/lib/kadm5/clnt/libkadm5clnt_mit.exports | 2 + src/lib/kadm5/kadm_err.et | 1 + src/lib/kadm5/kadm_rpc.h | 12 + src/lib/kadm5/kadm_rpc_xdr.c | 15 + src/lib/kadm5/server_internal.h | 7 + src/lib/kadm5/srv/kadm5_hook.c | 10 +- src/lib/kadm5/srv/libkadm5srv_mit.exports | 2 + src/lib/kadm5/srv/server_kdb.c | 4 +- src/lib/kadm5/srv/svr_principal.c | 49 ++- src/lib/kdb/kdb5.c | 112 +++++- src/lib/kdb/libkdb5.exports | 2 + src/lib/krb5/error_tables/kdb5_err.et | 1 + src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c | 227 ++++++----- src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h | 6 +- src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c | 429 ++++++++++++--------- src/tests/Makefile.in | 1 + src/tests/t_alias.py | 124 ++++++ src/tests/t_kadmin_acl.py | 102 ++++- src/tests/t_kdb.py | 44 ++- src/tests/t_tabdump.py | 6 + 40 files changed, 1238 insertions(+), 420 deletions(-) diff --git a/doc/admin/admin_commands/kadmin_local.rst b/doc/admin/admin_commands/kadmin_local.rst index 2435b3c36..b4edc7924 100644 --- a/doc/admin/admin_commands/kadmin_local.rst +++ b/doc/admin/admin_commands/kadmin_local.rst @@ -460,6 +460,24 @@ This command requires the **add** and **delete** privileges. Alias: **renprinc** +.. _add_alias: + +add_alias +~~~~~~~~~ + + **add_alias** *alias_princ* *target_princ* + +Create an alias *alias_princ* pointing to *target_princ*. Aliases may +be chained (that is, *target_princ* may itself be an alias) up to a +depth of 10. + +This command requires the **add** privilege for *alias_princ* and the +**modify** privilege for *target_princ*. + +(New in release 1.22.) + +Aliases: **alias** + .. _delete_principal: delete_principal @@ -467,8 +485,8 @@ delete_principal **delete_principal** [**-force**] *principal* -Deletes the specified *principal* from the database. This command -prompts for deletion, unless the **-force** option is given. +Deletes the specified *principal* or alias from the database. This +command prompts for deletion, unless the **-force** option is given. This command requires the **delete** privilege. diff --git a/doc/admin/admin_commands/kdb5_util.rst b/doc/admin/admin_commands/kdb5_util.rst index 444c58bcd..8147e9766 100644 --- a/doc/admin/admin_commands/kdb5_util.rst +++ b/doc/admin/admin_commands/kdb5_util.rst @@ -376,6 +376,14 @@ Options: Dump types: +**alias** + principal alias information + + **aliasname** + the name of the alias + **targetname** + the target of the alias + **keydata** principal encryption key information, including actual key data (which is still encrypted in the master key) diff --git a/doc/admin/conf_ldap.rst b/doc/admin/conf_ldap.rst index 65542c1a4..908dfd1e7 100644 --- a/doc/admin/conf_ldap.rst +++ b/doc/admin/conf_ldap.rst @@ -112,9 +112,10 @@ Configuring Kerberos with OpenLDAP back-end details. With the LDAP back end it is possible to provide aliases for principal -entries. Currently we provide no administrative utilities for -creating aliases, so it must be done by direct manipulation of the -LDAP entries. +entries. Beginning in release 1.22, aliases can be added with the +kadmin **add_alias** command, but it is also possible (in release 1.7 +or later) to provide aliases through direct manipulation of the LDAP +entries. An entry with aliases contains multiple values of the *krbPrincipalName* attribute. Since LDAP attribute values are not diff --git a/doc/admin/database.rst b/doc/admin/database.rst index 2fd07242a..685ec272f 100644 --- a/doc/admin/database.rst +++ b/doc/admin/database.rst @@ -93,6 +93,10 @@ To view the attributes of a principal, use the kadmin` To generate a listing of principals, use the kadmin **list_principals** command. +To give a principal additional names, use the kadmin **add_alias** +command to create aliases to the principal (new in release 1.22). +Aliases can be removed with the **delete_principal** command. + .. _policies: diff --git a/src/include/kdb.h b/src/include/kdb.h index d2252d505..7beee28df 100644 --- a/src/include/kdb.h +++ b/src/include/kdb.h @@ -263,6 +263,8 @@ typedef struct __krb5_key_salt_tuple { * must use the get_strings and set_string RPCs. */ #define KRB5_TL_STRING_ATTRS 0x000b +#define KRB5_TL_ALIAS_TARGET 0x000c + #define KRB5_TL_PAC_LOGON_INFO 0x0100 /* NDR encoded validation info */ #define KRB5_TL_SERVER_REFERRAL 0x0200 /* ASN.1 encoded ServerReferralInfo */ #define KRB5_TL_SVR_REFERRAL_DATA 0x0300 /* ASN.1 encoded PA-SVR-REFERRAL-DATA */ @@ -850,6 +852,17 @@ krb5_dbe_free_strings(krb5_context, krb5_string_attr *, int count); void krb5_dbe_free_string(krb5_context, char *); +/* Set *out to a stub alias entry pointing to target. */ +krb5_error_code +krb5_dbe_make_alias_entry(krb5_context context, krb5_const_principal alias, + krb5_const_principal target, krb5_db_entry **out); + +/* If entry contains a stub alias entry, set *target_out to the alias target. + * If not, set *target_out to NULL and return 0. */ +krb5_error_code +krb5_dbe_read_alias(krb5_context context, krb5_db_entry *entry, + krb5_principal *target_out); + /* * Register the KDB keytab type, allowing "KDB:" to be used as a keytab name. * For this type to work, the context used for keytab operations must have an @@ -1080,13 +1093,18 @@ typedef struct _kdb_vftabl { * an entry are expected to contain correct values, regardless of whether * they are specified in the mask, so it is acceptable for a module to * ignore the mask and update the entire entry. + * + * If the module has its own representation of principal aliases, this + * method should recognize alias stub entries using krb5_dbe_read_alias() + * and should create the alias instead of storing the stub entry. */ krb5_error_code (*put_principal)(krb5_context kcontext, krb5_db_entry *entry, char **db_args); /* - * Optional: Delete the entry for the principal search_for. If the - * principal did not exist, return KRB5_KDB_NOENTRY. + * Optional: Delete search_for from the database. If the principal did not + * exist, return KRB5_KDB_NOENTRY. If search_for is an alias, delete the + * alias, not the entry for the canonical principal. */ krb5_error_code (*delete_principal)(krb5_context kcontext, krb5_const_principal search_for); @@ -1094,7 +1112,7 @@ typedef struct _kdb_vftabl { /* * Optional with default: Rename a principal. If the source principal does * not exist, return KRB5_KDB_NOENTRY. If the target exists, return an - * error. + * error. This method will not be called if source is an alias. * * NOTE: If the module chooses to implement a custom function for renaming * a principal instead of using the default, then rename operations will @@ -1109,6 +1127,10 @@ typedef struct _kdb_vftabl { * arguments func_arg and the entry data. If match_entry is specified, the * module may narrow the iteration to principal names matching that regular * expression; a module may alternatively ignore match_entry. + * + * If the module has its own representation of principal aliases, this + * method should invoke func with a stub alias entry for each alias, + * created using krb5_dbe_make_alias_entry(). */ krb5_error_code (*iterate)(krb5_context kcontext, char *match_entry, diff --git a/src/include/krb5/kadm5_auth_plugin.h b/src/include/krb5/kadm5_auth_plugin.h index d514e99be..3f9392362 100644 --- a/src/include/krb5/kadm5_auth_plugin.h +++ b/src/include/krb5/kadm5_auth_plugin.h @@ -34,7 +34,7 @@ * * The kadm5_auth pluggable interface currently has only one supported major * version, which is 1. Major version 1 has a current minor version number of - * 1. + * 2. * * kadm5_auth plugin modules should define a function named * kadm5_auth__initvt, matching the signature: @@ -244,6 +244,13 @@ typedef krb5_error_code (*kadm5_auth_iprop_fn)(krb5_context context, kadm5_auth_moddata data, krb5_const_principal client); +/* Optional: authorize an add-alias operation. */ +typedef krb5_error_code +(*kadm5_auth_addalias_fn)(krb5_context context, kadm5_auth_moddata data, + krb5_const_principal client, + krb5_const_principal alias_princ, + krb5_const_principal target_princ); + /* * Optional: receive a notification that the most recent authorized operation * has ended. If a kadm5_auth module is also a KDB module, it can assume that @@ -301,6 +308,8 @@ typedef struct kadm5_auth_vtable_st { kadm5_auth_free_restrictions_fn free_restrictions; /* Minor version 1 ends here. */ + kadm5_auth_addalias_fn addalias; + /* Minor version 2 ends here. */ } *kadm5_auth_vtable; #endif /* KRB5_KADM5_AUTH_PLUGIN_H */ diff --git a/src/include/krb5/kadm5_hook_plugin.h b/src/include/krb5/kadm5_hook_plugin.h index f4f3730f4..cca6d6350 100644 --- a/src/include/krb5/kadm5_hook_plugin.h +++ b/src/include/krb5/kadm5_hook_plugin.h @@ -47,7 +47,7 @@ * does not provide strong guarantees of ABI stability. * * The kadm5_hook interface currently has only one supported major version, - * which is 1. Major version 1 has a current minor version number of 2. + * which is 1. Major version 1 has a current minor version number of 3. * * kadm5_hook plugins should: * kadm5_hook__initvt, matching the signature: @@ -149,6 +149,12 @@ typedef struct kadm5_hook_vtable_1_st { /* End of minor version 2. */ + kadm5_ret_t (*alias)(krb5_context context, kadm5_hook_modinfo *modinfo, + int stage, krb5_principal alias, + krb5_principal target); + + /* End of minor version 3. */ + } kadm5_hook_vftable_1; #endif /*H_KRB5_KADM5_HOOK_PLUGIN*/ diff --git a/src/kadmin/cli/kadmin.c b/src/kadmin/cli/kadmin.c index 372457039..f40c69e1c 100644 --- a/src/kadmin/cli/kadmin.c +++ b/src/kadmin/cli/kadmin.c @@ -774,6 +774,55 @@ cleanup: free(ocanon); } +void +kadmin_addalias(int argc, char *argv[], int sci_idx, void *info_ptr) +{ + kadm5_ret_t retval; + krb5_principal alias = NULL, target = NULL; + char *acanon = NULL, *tcanon = NULL; + + if (argc != 3) { + error(_("usage: add_alias alias_principal target_principal\n")); + return; + } + retval = kadmin_parse_name(argv[1], &alias); + if (retval) { + com_err("add_alias", retval, _("while parsing alias principal name")); + goto cleanup; + } + retval = kadmin_parse_name(argv[2], &target); + if (retval) { + com_err("add_alias", retval, _("while parsing target principal name")); + goto cleanup; + } + retval = krb5_unparse_name(context, alias, &acanon); + if (retval) { + com_err("add_alias", retval, + _("while canonicalizing alias principal")); + goto cleanup; + } + retval = krb5_unparse_name(context, target, &tcanon); + if (retval) { + com_err("add_alias", retval, + _("while canonicalizing target principal")); + goto cleanup; + } + retval = kadm5_create_alias(handle, alias, target); + if (retval) { + com_err("add_alias", retval, + _("while aliasing principal \"%s\" to \"%s\""), + acanon, tcanon); + goto cleanup; + } + info(_("Principal \"%s\" aliased to \"%s\".\n"), acanon, tcanon); + +cleanup: + krb5_free_principal(context, alias); + krb5_free_principal(context, target); + free(acanon); + free(tcanon); +} + static void cpw_usage(const char *str) { diff --git a/src/kadmin/cli/kadmin.h b/src/kadmin/cli/kadmin.h index f08eac153..bf63d0430 100644 --- a/src/kadmin/cli/kadmin.h +++ b/src/kadmin/cli/kadmin.h @@ -42,6 +42,8 @@ extern void kadmin_delprinc(int argc, char *argv[], int sci_idx, void *info_ptr); extern void kadmin_renameprinc(int argc, char *argv[], int sci_idx, void *info_ptr); +extern void kadmin_addalias(int argc, char *argv[], int sci_idx, + void *info_ptr); extern void kadmin_cpw(int argc, char *argv[], int sci_idx, void *info_ptr); extern void kadmin_addprinc(int argc, char *argv[], int sci_idx, void *info_ptr); diff --git a/src/kadmin/cli/kadmin_ct.ct b/src/kadmin/cli/kadmin_ct.ct index 705e41840..62fb4d6a8 100644 --- a/src/kadmin/cli/kadmin_ct.ct +++ b/src/kadmin/cli/kadmin_ct.ct @@ -38,6 +38,9 @@ request kadmin_modprinc, "Modify principal", request kadmin_renameprinc, "Rename principal", rename_principal, renprinc; +request kadmin_addalias, "Add alias", + add_alias, alias; + request kadmin_cpw, "Change password", change_password, cpw; diff --git a/src/kadmin/dbutil/tabdump.c b/src/kadmin/dbutil/tabdump.c index da55c2d78..d4b7b7333 100644 --- a/src/kadmin/dbutil/tabdump.c +++ b/src/kadmin/dbutil/tabdump.c @@ -69,6 +69,7 @@ struct tdtype { tdump_policy_fn *policy_fn; }; +static tdump_princ_fn alias; static tdump_princ_fn keydata; static tdump_princ_fn keyinfo; static tdump_princ_fn princ_flags; @@ -98,9 +99,13 @@ static char * const princ_stringattrs_fields[] = { static char * const princ_tktpolicy_fields[] = { "name", "expiration", "pw_expiration", "max_life", "max_renew_life", NULL }; +static char * const alias_fields[] = { + "aliasname", "targetname", NULL +}; /* Lookup table for tabdump record types */ static struct tdtype tdtypes[] = { + {"alias", alias_fields, alias, NULL}, {"keydata", keydata_fields, keydata, NULL}, {"keyinfo", keyinfo_fields, keyinfo, NULL}, {"princ_flags", princ_flags_fields, princ_flags, NULL}, @@ -252,6 +257,39 @@ write_data(struct rec_args *args, krb5_data *data) return ret; } +static krb5_error_code +alias(struct rec_args *args, const char *name, krb5_db_entry *dbe) +{ + krb5_error_code ret; + struct rechandle *h = args->rh; + krb5_principal target = NULL; + char *tname = NULL; + + ret = krb5_dbe_read_alias(util_context, dbe, &target); + if (ret) + return ret; + if (target == NULL) + return 0; + + ret = krb5_unparse_name(util_context, target, &tname); + if (ret) + goto cleanup; + + if (startrec(h) < 0) + ret = errno; + if (!ret && writefield(h, "%s", name) < 0) + ret = errno; + if (!ret && writefield(h, "%s", tname) < 0) + ret = errno; + if (!ret && endrec(h) < 0) + ret = errno; + +cleanup: + krb5_free_principal(util_context, target); + krb5_free_unparsed_name(util_context, tname); + return ret; +} + /* Write a single record of a keydata/keyinfo key set. */ static krb5_error_code keyinfo_rec(struct rec_args *args, const char *name, int i, krb5_key_data *kd, diff --git a/src/kadmin/server/auth.c b/src/kadmin/server/auth.c index 081b20a8b..224b12190 100644 --- a/src/kadmin/server/auth.c +++ b/src/kadmin/server/auth.c @@ -91,7 +91,7 @@ auth_init(krb5_context context, const char *acl_file) h = k5alloc(sizeof(*h), &ret); if (h == NULL) goto cleanup; - ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt); + ret = (*mod)(context, 1, 2, (krb5_plugin_vtable)&h->vt); if (ret) { /* Failed vtable init is non-fatal. */ TRACE_KADM5_AUTH_VTINIT_FAIL(context, ret); free(h); @@ -172,6 +172,8 @@ call_module(krb5_context context, auth_handle h, int opcode, return h->vt.listpols(context, h->data, client); else if (opcode == OP_IPROP && h->vt.iprop != NULL) return h->vt.iprop(context, h->data, client); + else if (opcode == OP_ADDALIAS && h->vt.addalias != NULL) + return h->vt.addalias(context, h->data, client, p1, p2); return KRB5_PLUGIN_NO_HANDLE; } @@ -264,12 +266,12 @@ impose_restrictions(krb5_context context, krb5_boolean auth_restrict(krb5_context context, int opcode, krb5_const_principal client, - kadm5_principal_ent_t ent, long *mask) + krb5_const_principal target, kadm5_principal_ent_t ent, + long *mask) { auth_handle *hp, h; krb5_boolean authorized = FALSE; krb5_error_code ret, rs_ret; - krb5_const_principal target = ent->principal; struct kadm5_auth_restrictions *rs; assert(opcode == OP_ADDPRINC || opcode == OP_MODPRINC); diff --git a/src/kadmin/server/auth.h b/src/kadmin/server/auth.h index 4d265add7..5f51a9911 100644 --- a/src/kadmin/server/auth.h +++ b/src/kadmin/server/auth.h @@ -52,6 +52,7 @@ #define OP_GETPOL 17 #define OP_LISTPOLS 18 #define OP_IPROP 19 +#define OP_ADDALIAS 20 /* Initialize all authorization modules. */ krb5_error_code auth_init(krb5_context context, const char *acl_file); @@ -70,6 +71,7 @@ krb5_boolean auth(krb5_context context, int opcode, * restrictions to ent and mask if any modules supply them. */ krb5_boolean auth_restrict(krb5_context context, int opcode, krb5_const_principal client, + krb5_const_principal target, kadm5_principal_ent_t ent, long *mask); /* Notify modules that the most recent authorized operation has ended. */ diff --git a/src/kadmin/server/auth_acl.c b/src/kadmin/server/auth_acl.c index ce9ace36a..a04fb23db 100644 --- a/src/kadmin/server/auth_acl.c +++ b/src/kadmin/server/auth_acl.c @@ -720,6 +720,19 @@ acl_iprop(krb5_context context, kadm5_auth_moddata data, return acl_check(data, ACL_IPROP, client, NULL, NULL); } +static krb5_error_code +acl_addalias(krb5_context context, kadm5_auth_moddata data, + krb5_const_principal client, krb5_const_principal alias, + krb5_const_principal target) +{ + struct kadm5_auth_restrictions *rs; + + if (acl_check(data, ACL_ADD, client, alias, &rs) == 0 && rs == NULL && + acl_check(data, ACL_MODIFY, client, target, &rs) == 0) + return 0; + return KRB5_PLUGIN_NO_HANDLE; +} + krb5_error_code kadm5_auth_acl_initvt(krb5_context context, int maj_ver, int min_ver, krb5_plugin_vtable vtable) @@ -751,5 +764,6 @@ kadm5_auth_acl_initvt(krb5_context context, int maj_ver, int min_ver, vt->getpol = acl_getpol; vt->listpols = acl_listpols; vt->iprop = acl_iprop; + vt->addalias = acl_addalias; return 0; } diff --git a/src/kadmin/server/kadm_rpc_svc.c b/src/kadmin/server/kadm_rpc_svc.c index f0e43d9ae..867d8b040 100644 --- a/src/kadmin/server/kadm_rpc_svc.c +++ b/src/kadmin/server/kadm_rpc_svc.c @@ -59,6 +59,7 @@ kadm_1(struct svc_req *rqstp, SVCXPRT *transp) setkey3_arg setkey_principal3_2_arg; setkey4_arg setkey_principal4_2_arg; getpkeys_arg get_principal_keys_2_arg; + calias_arg create_alias_2_arg; } argument; union { generic_ret gen_ret; @@ -241,6 +242,12 @@ kadm_1(struct svc_req *rqstp, SVCXPRT *transp) local = (bool_t (*)(char *, void *, struct svc_req *))get_principal_keys_2_svc; break; + case CREATE_ALIAS: + xdr_argument = (xdrproc_t)xdr_calias_arg; + xdr_result = (xdrproc_t)xdr_generic_ret; + local = (bool_t (*)(char *, void *, struct svc_req *))create_alias_2_svc; + break; + default: krb5_klog_syslog(LOG_ERR, "Invalid KADM5 procedure number: %s, %d", client_addr(rqstp->rq_xprt), rqstp->rq_proc); diff --git a/src/kadmin/server/server_stubs.c b/src/kadmin/server/server_stubs.c index 87f67874a..b2371e67a 100644 --- a/src/kadmin/server/server_stubs.c +++ b/src/kadmin/server/server_stubs.c @@ -265,7 +265,7 @@ static kadm5_ret_t stub_setup(krb5_ui_4 api_version, struct svc_req *rqstp, krb5_principal princ, kadm5_server_handle_t *handle_out, krb5_ui_4 *api_version_out, gss_buffer_t client_name_out, gss_buffer_t service_name_out, - char **princ_str_out) + char **princ_str_out, kadm5_principal_ent_rec *rec_out) { kadm5_ret_t ret; @@ -289,6 +289,13 @@ stub_setup(krb5_ui_4 api_version, struct svc_req *rqstp, krb5_principal princ, return KADM5_BAD_PRINCIPAL; } + if (rec_out != NULL) { + if (princ == NULL) + return KADM5_BAD_PRINCIPAL; + return kadm5_get_principal(*handle_out, princ, rec_out, + KADM5_PRINCIPAL | KADM5_ATTRIBUTES); + } + return KADM5_OK; } @@ -324,10 +331,11 @@ stub_auth_pol(kadm5_server_handle_t handle, int opcode, const char *policy, static krb5_boolean stub_auth_restrict(kadm5_server_handle_t handle, int opcode, - kadm5_principal_ent_t ent, long *mask) + krb5_const_principal princ, kadm5_principal_ent_t ent, + long *mask) { return auth_restrict(handle->context, opcode, handle->current_caller, - ent, mask); + princ, ent, mask); } /* Return true if the client authenticated to kadmin/changepw and princ is not @@ -440,12 +448,13 @@ create_principal_2_svc(cprinc_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->rec.principal, &handle, &ret->api_version, &client_name, - &service_name, &prime_arg); + &service_name, &prime_arg, NULL); if (ret->code) goto exit_func; if (CHANGEPW_SERVICE(rqstp) || - !stub_auth_restrict(handle, OP_ADDPRINC, &arg->rec, &arg->mask)) { + !stub_auth_restrict(handle, OP_ADDPRINC, arg->rec.principal, + &arg->rec, &arg->mask)) { ret->code = KADM5_AUTH_ADD; log_unauth("kadm5_create_principal", prime_arg, &client_name, &service_name, rqstp); @@ -480,12 +489,13 @@ create_principal3_2_svc(cprinc3_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->rec.principal, &handle, &ret->api_version, &client_name, - &service_name, &prime_arg); + &service_name, &prime_arg, NULL); if (ret->code) goto exit_func; if (CHANGEPW_SERVICE(rqstp) || - !stub_auth_restrict(handle, OP_ADDPRINC, &arg->rec, &arg->mask)) { + !stub_auth_restrict(handle, OP_ADDPRINC, arg->rec.principal, &arg->rec, + &arg->mask)) { ret->code = KADM5_AUTH_ADD; log_unauth("kadm5_create_principal", prime_arg, &client_name, &service_name, rqstp); @@ -508,9 +518,15 @@ exit_func: return TRUE; } -/* Return KADM5_PROTECT_KEYS if KRB5_KDB_LOCKDOWN_KEYS is set for princ. */ +/* Return KADM5_PROTECT_KEYS if KRB5_KDB_LOCKDOWN_KEYS is set for rec. */ +static inline kadm5_ret_t +check_lockdown(kadm5_principal_ent_t rec) +{ + return (rec->attributes & KRB5_KDB_LOCKDOWN_KEYS) ? KADM5_PROTECT_KEYS : 0; +} + static kadm5_ret_t -check_lockdown_keys(kadm5_server_handle_t handle, krb5_principal princ) +check_lockdown_by_princ(kadm5_server_handle_t handle, krb5_principal princ) { kadm5_principal_ent_rec rec; kadm5_ret_t ret; @@ -518,7 +534,7 @@ check_lockdown_keys(kadm5_server_handle_t handle, krb5_principal princ) ret = kadm5_get_principal(handle, princ, &rec, KADM5_ATTRIBUTES); if (ret) return ret; - ret = (rec.attributes & KRB5_KDB_LOCKDOWN_KEYS) ? KADM5_PROTECT_KEYS : 0; + ret = check_lockdown(&rec); kadm5_free_principal_ent(handle, &rec); return ret; } @@ -535,7 +551,7 @@ delete_principal_2_svc(dprinc_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, NULL); if (ret->code) goto exit_func; @@ -545,7 +561,7 @@ delete_principal_2_svc(dprinc_arg *arg, generic_ret *ret, log_unauth("kadm5_delete_principal", prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = check_lockdown_keys(handle, arg->princ); + ret->code = check_lockdown_by_princ(handle, arg->princ); if (ret->code == KADM5_PROTECT_KEYS) { log_unauth("kadm5_delete_principal", prime_arg, &client_name, &service_name, rqstp); @@ -577,6 +593,7 @@ modify_principal_2_svc(mprinc_arg *arg, generic_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }, rec_copy; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -584,18 +601,19 @@ modify_principal_2_svc(mprinc_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->rec.principal, &handle, &ret->api_version, &client_name, - &service_name, &prime_arg); + &service_name, &prime_arg, &rec); if (ret->code) goto exit_func; if (CHANGEPW_SERVICE(rqstp) || - !stub_auth_restrict(handle, OP_MODPRINC, &arg->rec, &arg->mask)) { + !stub_auth_restrict(handle, OP_MODPRINC, rec.principal, &arg->rec, + &arg->mask)) { ret->code = KADM5_AUTH_MODIFY; log_unauth("kadm5_modify_principal", prime_arg, &client_name, &service_name, rqstp); } else if ((arg->mask & KADM5_ATTRIBUTES) && (!(arg->rec.attributes & KRB5_KDB_LOCKDOWN_KEYS))) { - ret->code = check_lockdown_keys(handle, arg->rec.principal); + ret->code = check_lockdown(&rec); if (ret->code == KADM5_PROTECT_KEYS) { log_unauth("kadm5_modify_principal", prime_arg, &client_name, &service_name, rqstp); @@ -604,7 +622,11 @@ modify_principal_2_svc(mprinc_arg *arg, generic_ret *ret, } if (ret->code == KADM5_OK) { - ret->code = kadm5_modify_principal(handle, &arg->rec, arg->mask); + /* Modify via the canonicalized principal name using a shallow copy of + * arg->rec, to ensure consistency with the ACL check. */ + rec_copy = arg->rec; + rec_copy.principal = rec.principal; + ret->code = kadm5_modify_principal(handle, &rec_copy, arg->mask); if (ret->code != 0) errmsg = krb5_get_error_message(handle->context, ret->code); @@ -616,6 +638,7 @@ modify_principal_2_svc(mprinc_arg *arg, generic_ret *ret, } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -634,7 +657,7 @@ rename_principal_2_svc(rprinc_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle, &ret->api_version, &client_name, &service_name, - NULL); + NULL, NULL); if (ret->code) goto exit_func; @@ -658,7 +681,7 @@ rename_principal_2_svc(rprinc_arg *arg, generic_ret *ret, log_unauth("kadm5_rename_principal", prime_arg1, &client_name, &service_name, rqstp); } else { - ret->code = check_lockdown_keys(handle, arg->src); + ret->code = check_lockdown_by_princ(handle, arg->src); if (ret->code == KADM5_PROTECT_KEYS) { log_unauth("kadm5_rename_principal", prime_arg1, &client_name, &service_name, rqstp); @@ -708,6 +731,7 @@ bool_t get_principal_2_svc(gprinc_arg *arg, gprinc_ret *ret, struct svc_req *rqstp) { char *funcname, *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -715,19 +739,19 @@ get_principal_2_svc(gprinc_arg *arg, gprinc_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; funcname = "kadm5_get_principal"; - if (changepw_not_self(handle, rqstp, arg->princ) || - !stub_auth(handle, OP_GETPRINC, arg->princ, NULL, NULL, NULL)) { + if (changepw_not_self(handle, rqstp, rec.principal) || + !stub_auth(handle, OP_GETPRINC, rec.principal, NULL, NULL, NULL)) { ret->code = KADM5_AUTH_GET; log_unauth(funcname, prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = kadm5_get_principal(handle, arg->princ, &ret->rec, + ret->code = kadm5_get_principal(handle, rec.principal, &ret->rec, arg->mask); if (ret->code != 0) @@ -741,6 +765,7 @@ get_principal_2_svc(gprinc_arg *arg, gprinc_ret *ret, struct svc_req *rqstp) } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -756,7 +781,7 @@ get_princs_2_svc(gprincs_arg *arg, gprincs_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle, &ret->api_version, &client_name, &service_name, - NULL); + NULL, NULL); if (ret->code) goto exit_func; @@ -793,6 +818,7 @@ chpass_principal_2_svc(chpass_arg *arg, generic_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -800,26 +826,28 @@ chpass_principal_2_svc(chpass_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; - ret->code = check_lockdown_keys(handle, arg->princ); + ret->code = check_lockdown(&rec); if (ret->code != KADM5_OK) { if (ret->code == KADM5_PROTECT_KEYS) { log_unauth("kadm5_chpass_principal", prime_arg, &client_name, &service_name, rqstp); ret->code = KADM5_AUTH_CHANGEPW; } - } else if (changepw_not_self(handle, rqstp, arg->princ) || - !stub_auth(handle, OP_CPW, arg->princ, NULL, NULL, NULL)) { + } else if (changepw_not_self(handle, rqstp, rec.principal) || + !stub_auth(handle, OP_CPW, rec.principal, NULL, NULL, NULL)) { ret->code = KADM5_AUTH_CHANGEPW; log_unauth("kadm5_chpass_principal", prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = check_self_keychange(handle, rqstp, arg->princ); - if (!ret->code) - ret->code = kadm5_chpass_principal(handle, arg->princ, arg->pass); + ret->code = check_self_keychange(handle, rqstp, rec.principal); + if (!ret->code) { + ret->code = kadm5_chpass_principal(handle, rec.principal, + arg->pass); + } } if (ret->code != KADM5_AUTH_CHANGEPW) { @@ -834,6 +862,7 @@ chpass_principal_2_svc(chpass_arg *arg, generic_ret *ret, } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -843,6 +872,7 @@ chpass_principal3_2_svc(chpass3_arg *arg, generic_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -850,26 +880,26 @@ chpass_principal3_2_svc(chpass3_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; - ret->code = check_lockdown_keys(handle, arg->princ); + ret->code = check_lockdown(&rec); if (ret->code != KADM5_OK) { if (ret->code == KADM5_PROTECT_KEYS) { log_unauth("kadm5_chpass_principal", prime_arg, &client_name, &service_name, rqstp); ret->code = KADM5_AUTH_CHANGEPW; } - } else if (changepw_not_self(handle, rqstp, arg->princ) || - !stub_auth(handle, OP_CPW, arg->princ, NULL, NULL, NULL)) { + } else if (changepw_not_self(handle, rqstp, rec.principal) || + !stub_auth(handle, OP_CPW, rec.principal, NULL, NULL, NULL)) { ret->code = KADM5_AUTH_CHANGEPW; log_unauth("kadm5_chpass_principal", prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = check_self_keychange(handle, rqstp, arg->princ); + ret->code = check_self_keychange(handle, rqstp, rec.principal); if (!ret->code) { - ret->code = kadm5_chpass_principal_3(handle, arg->princ, + ret->code = kadm5_chpass_principal_3(handle, rec.principal, arg->keepold, arg->n_ks_tuple, arg->ks_tuple, arg->pass); } @@ -887,6 +917,7 @@ chpass_principal3_2_svc(chpass3_arg *arg, generic_ret *ret, } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -896,6 +927,7 @@ setkey_principal_2_svc(setkey_arg *arg, generic_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -903,11 +935,11 @@ setkey_principal_2_svc(setkey_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; - ret->code = check_lockdown_keys(handle, arg->princ); + ret->code = check_lockdown(&rec); if (ret->code != KADM5_OK) { if (ret->code == KADM5_PROTECT_KEYS) { log_unauth("kadm5_setkey_principal", prime_arg, &client_name, @@ -915,9 +947,9 @@ setkey_principal_2_svc(setkey_arg *arg, generic_ret *ret, ret->code = KADM5_AUTH_SETKEY; } } else if (!(CHANGEPW_SERVICE(rqstp)) && - stub_auth(handle, OP_SETKEY, arg->princ, NULL, NULL, NULL)) { - ret->code = kadm5_setkey_principal(handle, arg->princ, arg->keyblocks, - arg->n_keys); + stub_auth(handle, OP_SETKEY, rec.principal, NULL, NULL, NULL)) { + ret->code = kadm5_setkey_principal(handle, rec.principal, + arg->keyblocks, arg->n_keys); } else { log_unauth("kadm5_setkey_principal", prime_arg, &client_name, &service_name, rqstp); @@ -936,6 +968,7 @@ setkey_principal_2_svc(setkey_arg *arg, generic_ret *ret, } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -945,6 +978,7 @@ setkey_principal3_2_svc(setkey3_arg *arg, generic_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -952,11 +986,11 @@ setkey_principal3_2_svc(setkey3_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; - ret->code = check_lockdown_keys(handle, arg->princ); + ret->code = check_lockdown(&rec); if (ret->code != KADM5_OK) { if (ret->code == KADM5_PROTECT_KEYS) { log_unauth("kadm5_setkey_principal", prime_arg, &client_name, @@ -964,10 +998,11 @@ setkey_principal3_2_svc(setkey3_arg *arg, generic_ret *ret, ret->code = KADM5_AUTH_SETKEY; } } else if (!(CHANGEPW_SERVICE(rqstp)) && - stub_auth(handle, OP_SETKEY, arg->princ, NULL, NULL, NULL)) { - ret->code = kadm5_setkey_principal_3(handle, arg->princ, arg->keepold, - arg->n_ks_tuple, arg->ks_tuple, - arg->keyblocks, arg->n_keys); + stub_auth(handle, OP_SETKEY, rec.principal, NULL, NULL, NULL)) { + ret->code = kadm5_setkey_principal_3(handle, rec.principal, + arg->keepold, arg->n_ks_tuple, + arg->ks_tuple, arg->keyblocks, + arg->n_keys); } else { log_unauth("kadm5_setkey_principal", prime_arg, &client_name, &service_name, rqstp); @@ -986,6 +1021,7 @@ setkey_principal3_2_svc(setkey3_arg *arg, generic_ret *ret, } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -995,6 +1031,7 @@ setkey_principal4_2_svc(setkey4_arg *arg, generic_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -1002,11 +1039,11 @@ setkey_principal4_2_svc(setkey4_arg *arg, generic_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; - ret->code = check_lockdown_keys(handle, arg->princ); + ret->code = check_lockdown(&rec); if (ret->code != KADM5_OK) { if (ret->code == KADM5_PROTECT_KEYS) { log_unauth("kadm5_setkey_principal", prime_arg, &client_name, @@ -1014,9 +1051,10 @@ setkey_principal4_2_svc(setkey4_arg *arg, generic_ret *ret, ret->code = KADM5_AUTH_SETKEY; } } else if (!(CHANGEPW_SERVICE(rqstp)) && - stub_auth(handle, OP_SETKEY, arg->princ, NULL, NULL, NULL)) { - ret->code = kadm5_setkey_principal_4(handle, arg->princ, arg->keepold, - arg->key_data, arg->n_key_data); + stub_auth(handle, OP_SETKEY, rec.principal, NULL, NULL, NULL)) { + ret->code = kadm5_setkey_principal_4(handle, rec.principal, + arg->keepold, arg->key_data, + arg->n_key_data); } else { log_unauth("kadm5_setkey_principal", prime_arg, &client_name, &service_name, rqstp); @@ -1035,6 +1073,7 @@ setkey_principal4_2_svc(setkey4_arg *arg, generic_ret *ret, } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -1042,13 +1081,13 @@ exit_func: /* Empty out *keys / *nkeys if princ is protected with the lockdown * attribute, or if we fail to check. */ static kadm5_ret_t -chrand_check_lockdown(kadm5_server_handle_t handle, krb5_principal princ, +chrand_check_lockdown(kadm5_server_handle_t handle, kadm5_principal_ent_t rec, krb5_keyblock **keys, int *nkeys) { kadm5_ret_t ret; int i; - ret = check_lockdown_keys(handle, princ); + ret = check_lockdown(rec); if (!ret) return 0; @@ -1064,6 +1103,7 @@ bool_t chrand_principal_2_svc(chrand_arg *arg, chrand_ret *ret, struct svc_req *rqstp) { char *funcname, *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; krb5_keyblock *k; @@ -1073,27 +1113,27 @@ chrand_principal_2_svc(chrand_arg *arg, chrand_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; funcname = "kadm5_randkey_principal"; - if (changepw_not_self(handle, rqstp, arg->princ) || - !stub_auth(handle, OP_CHRAND, arg->princ, NULL, NULL, NULL)) { + if (changepw_not_self(handle, rqstp, rec.principal) || + !stub_auth(handle, OP_CHRAND, rec.principal, NULL, NULL, NULL)) { ret->code = KADM5_AUTH_CHANGEPW; log_unauth(funcname, prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = check_self_keychange(handle, rqstp, arg->princ); + ret->code = check_self_keychange(handle, rqstp, rec.principal); if (!ret->code) { - ret->code = kadm5_randkey_principal(handle, arg->princ, + ret->code = kadm5_randkey_principal(handle, rec.principal, &k, &nkeys); } } if (ret->code == KADM5_OK) { - ret->code = chrand_check_lockdown(handle, arg->princ, &k, &nkeys); + ret->code = chrand_check_lockdown(handle, &rec, &k, &nkeys); if (ret->code == KADM5_PROTECT_KEYS) ret->code = KADM5_OK; ret->keys = k; @@ -1112,6 +1152,7 @@ chrand_principal_2_svc(chrand_arg *arg, chrand_ret *ret, struct svc_req *rqstp) } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -1121,6 +1162,7 @@ chrand_principal3_2_svc(chrand3_arg *arg, chrand_ret *ret, struct svc_req *rqstp) { char *funcname, *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; krb5_keyblock *k; @@ -1130,21 +1172,21 @@ chrand_principal3_2_svc(chrand3_arg *arg, chrand_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; funcname = "kadm5_randkey_principal"; - if (changepw_not_self(handle, rqstp, arg->princ) || - !stub_auth(handle, OP_CHRAND, arg->princ, NULL, NULL, NULL)) { + if (changepw_not_self(handle, rqstp, rec.principal) || + !stub_auth(handle, OP_CHRAND, rec.principal, NULL, NULL, NULL)) { ret->code = KADM5_AUTH_CHANGEPW; log_unauth(funcname, prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = check_self_keychange(handle, rqstp, arg->princ); + ret->code = check_self_keychange(handle, rqstp, rec.principal); if (!ret->code) { - ret->code = kadm5_randkey_principal_3(handle, arg->princ, + ret->code = kadm5_randkey_principal_3(handle, rec.principal, arg->keepold, arg->n_ks_tuple, arg->ks_tuple, &k, &nkeys); @@ -1152,7 +1194,7 @@ chrand_principal3_2_svc(chrand3_arg *arg, chrand_ret *ret, } if (ret->code == KADM5_OK) { - ret->code = chrand_check_lockdown(handle, arg->princ, &k, &nkeys); + ret->code = chrand_check_lockdown(handle, &rec, &k, &nkeys); if (ret->code == KADM5_PROTECT_KEYS) ret->code = KADM5_OK; ret->keys = k; @@ -1171,6 +1213,7 @@ chrand_principal3_2_svc(chrand3_arg *arg, chrand_ret *ret, } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -1186,7 +1229,7 @@ create_policy_2_svc(cpol_arg *arg, generic_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle, &ret->api_version, &client_name, &service_name, - NULL); + NULL, NULL); if (ret->code) goto exit_func; @@ -1228,7 +1271,7 @@ delete_policy_2_svc(dpol_arg *arg, generic_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle, &ret->api_version, &client_name, &service_name, - NULL); + NULL, NULL); if (ret->code) goto exit_func; @@ -1268,7 +1311,7 @@ modify_policy_2_svc(mpol_arg *arg, generic_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle, &ret->api_version, &client_name, &service_name, - NULL); + NULL, NULL); if (ret->code) goto exit_func; @@ -1313,7 +1356,7 @@ get_policy_2_svc(gpol_arg *arg, gpol_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle, &ret->api_version, &client_name, &service_name, - NULL); + NULL, NULL); if (ret->code) goto exit_func; @@ -1362,7 +1405,7 @@ get_pols_2_svc(gpols_arg *arg, gpols_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle, &ret->api_version, &client_name, &service_name, - NULL); + NULL, NULL); if (ret->code) goto exit_func; @@ -1402,7 +1445,7 @@ get_privs_2_svc(krb5_ui_4 *arg, getprivs_ret *ret, struct svc_req *rqstp) const char *errmsg = NULL; ret->code = stub_setup(*arg, rqstp, NULL, &handle, &ret->api_version, - &client_name, &service_name, NULL); + &client_name, &service_name, NULL, NULL); if (ret->code) goto exit_func; @@ -1425,6 +1468,7 @@ bool_t purgekeys_2_svc(purgekeys_arg *arg, generic_ret *ret, struct svc_req *rqstp) { char *funcname, *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -1433,18 +1477,18 @@ purgekeys_2_svc(purgekeys_arg *arg, generic_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; funcname = "kadm5_purgekeys"; if (CHANGEPW_SERVICE(rqstp) || - !stub_auth(handle, OP_PURGEKEYS, arg->princ, NULL, NULL, NULL)) { + !stub_auth(handle, OP_PURGEKEYS, rec.principal, NULL, NULL, NULL)) { ret->code = KADM5_AUTH_MODIFY; log_unauth(funcname, prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = kadm5_purgekeys(handle, arg->princ, arg->keepkvno); + ret->code = kadm5_purgekeys(handle, rec.principal, arg->keepkvno); if (ret->code != 0) errmsg = krb5_get_error_message(handle->context, ret->code); @@ -1456,6 +1500,7 @@ purgekeys_2_svc(purgekeys_arg *arg, generic_ret *ret, struct svc_req *rqstp) } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -1464,6 +1509,7 @@ bool_t get_strings_2_svc(gstrings_arg *arg, gstrings_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -1471,17 +1517,17 @@ get_strings_2_svc(gstrings_arg *arg, gstrings_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; if (CHANGEPW_SERVICE(rqstp) || - !stub_auth(handle, OP_GETSTRS, arg->princ, NULL, NULL, NULL)) { + !stub_auth(handle, OP_GETSTRS, rec.principal, NULL, NULL, NULL)) { ret->code = KADM5_AUTH_GET; log_unauth("kadm5_get_strings", prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = kadm5_get_strings(handle, arg->princ, &ret->strings, + ret->code = kadm5_get_strings(handle, rec.principal, &ret->strings, &ret->count); if (ret->code != 0) errmsg = krb5_get_error_message(handle->context, ret->code); @@ -1494,6 +1540,7 @@ get_strings_2_svc(gstrings_arg *arg, gstrings_ret *ret, struct svc_req *rqstp) } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -1502,6 +1549,7 @@ bool_t set_string_2_svc(sstring_arg *arg, generic_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -1509,18 +1557,19 @@ set_string_2_svc(sstring_arg *arg, generic_ret *ret, struct svc_req *rqstp) ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; if (CHANGEPW_SERVICE(rqstp) || - !stub_auth(handle, OP_SETSTR, arg->princ, NULL, + !stub_auth(handle, OP_SETSTR, rec.principal, NULL, arg->key, arg->value)) { ret->code = KADM5_AUTH_MODIFY; log_unauth("kadm5_mod_strings", prime_arg, &client_name, &service_name, rqstp); } else { - ret->code = kadm5_set_string(handle, arg->princ, arg->key, arg->value); + ret->code = kadm5_set_string(handle, rec.principal, + arg->key, arg->value); if (ret->code != 0) errmsg = krb5_get_error_message(handle->context, ret->code); @@ -1532,6 +1581,7 @@ set_string_2_svc(sstring_arg *arg, generic_ret *ret, struct svc_req *rqstp) } exit_func: + kadm5_free_principal_ent(handle, &rec); stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; } @@ -1547,7 +1597,7 @@ init_2_svc(krb5_ui_4 *arg, generic_ret *ret, struct svc_req *rqstp) char *cdots, *sdots; ret->code = stub_setup(*arg, rqstp, NULL, &handle, &ret->api_version, - &client_name, &service_name, NULL); + &client_name, &service_name, NULL, NULL); if (ret->code) goto exit_func; @@ -1592,6 +1642,7 @@ get_principal_keys_2_svc(getpkeys_arg *arg, getpkeys_ret *ret, struct svc_req *rqstp) { char *prime_arg = NULL; + kadm5_principal_ent_rec rec = { 0 }; gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; kadm5_server_handle_t handle; @@ -1599,13 +1650,13 @@ get_principal_keys_2_svc(getpkeys_arg *arg, getpkeys_ret *ret, ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle, &ret->api_version, &client_name, &service_name, - &prime_arg); + &prime_arg, &rec); if (ret->code) goto exit_func; if (!(CHANGEPW_SERVICE(rqstp)) && - stub_auth(handle, OP_EXTRACT, arg->princ, NULL, NULL, NULL)) { - ret->code = kadm5_get_principal_keys(handle, arg->princ, arg->kvno, + stub_auth(handle, OP_EXTRACT, rec.principal, NULL, NULL, NULL)) { + ret->code = kadm5_get_principal_keys(handle, rec.principal, arg->kvno, &ret->key_data, &ret->n_key_data); } else { log_unauth("kadm5_get_principal_keys", prime_arg, @@ -1614,7 +1665,7 @@ get_principal_keys_2_svc(getpkeys_arg *arg, getpkeys_ret *ret, } if (ret->code == KADM5_OK) { - ret->code = check_lockdown_keys(handle, arg->princ); + ret->code = check_lockdown(&rec); if (ret->code != KADM5_OK) { kadm5_free_kadm5_key_data(handle->context, ret->n_key_data, ret->key_data); @@ -1639,6 +1690,42 @@ get_principal_keys_2_svc(getpkeys_arg *arg, getpkeys_ret *ret, krb5_free_error_message(handle->context, errmsg); } +exit_func: + kadm5_free_principal_ent(handle, &rec); + stub_cleanup(handle, prime_arg, &client_name, &service_name); + return TRUE; +} + +bool_t +create_alias_2_svc(calias_arg *arg, generic_ret *ret, struct svc_req *rqstp) +{ + char *prime_arg = NULL; + gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER; + gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER; + kadm5_server_handle_t handle; + const char *errmsg = NULL; + + ret->code = stub_setup(arg->api_version, rqstp, arg->alias, &handle, + &ret->api_version, &client_name, &service_name, + &prime_arg, NULL); + if (ret->code) + goto exit_func; + + if (CHANGEPW_SERVICE(rqstp) || + !stub_auth(handle, OP_ADDALIAS, arg->alias, arg->target, NULL, NULL)) { + ret->code = KADM5_AUTH_INSUFFICIENT; + log_unauth("kadm5_create_alias", prime_arg, &client_name, + &service_name, rqstp); + } else { + ret->code = kadm5_create_alias(handle, arg->alias, arg->target); + if (ret->code) + errmsg = krb5_get_error_message(handle->context, ret->code); + log_done("kadm5_create_alias", prime_arg, errmsg, &client_name, + &service_name, rqstp); + if (errmsg != NULL) + krb5_free_error_message(handle->context, errmsg); + } + exit_func: stub_cleanup(handle, prime_arg, &client_name, &service_name); return TRUE; diff --git a/src/lib/kadm5/admin.h b/src/lib/kadm5/admin.h index 296c86fa6..4af1ea225 100644 --- a/src/lib/kadm5/admin.h +++ b/src/lib/kadm5/admin.h @@ -495,6 +495,9 @@ kadm5_ret_t kadm5_free_strings(void *server_handle, kadm5_ret_t kadm5_free_kadm5_key_data(krb5_context context, int n_key_data, kadm5_key_data *key_data); +kadm5_ret_t kadm5_create_alias(void *server_handle, krb5_principal alias, + krb5_principal target); + KADM5INT_END_DECLS #endif /* __KADM5_ADMIN_H__ */ diff --git a/src/lib/kadm5/admin_xdr.h b/src/lib/kadm5/admin_xdr.h index 9da98451e..12a2598c2 100644 --- a/src/lib/kadm5/admin_xdr.h +++ b/src/lib/kadm5/admin_xdr.h @@ -71,3 +71,4 @@ bool_t xdr_osa_pw_hist_ent(XDR *xdrs, osa_pw_hist_ent *objp); bool_t xdr_kadm5_key_data(XDR *xdrs, kadm5_key_data *objp); bool_t xdr_getpkeys_arg(XDR *xdrs, getpkeys_arg *objp); bool_t xdr_getpkeys_ret(XDR *xdrs, getpkeys_ret *objp); +bool_t xdr_calias_arg(XDR *xdrs, calias_arg *objp); diff --git a/src/lib/kadm5/clnt/client_principal.c b/src/lib/kadm5/clnt/client_principal.c index 96d9d1932..976490093 100644 --- a/src/lib/kadm5/clnt/client_principal.c +++ b/src/lib/kadm5/clnt/client_principal.c @@ -526,3 +526,23 @@ kadm5_get_principal_keys(void *server_handle, krb5_principal princ, } return r.code; } + +kadm5_ret_t +kadm5_create_alias(void *server_handle, krb5_principal alias, + krb5_principal target) +{ + calias_arg arg; + generic_ret r = { 0, 0 }; + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + arg.alias = alias; + arg.target = target; + arg.api_version = handle->api_version; + if (alias == NULL || target == NULL) + return EINVAL; + if (create_alias_2(&arg, &r, handle->clnt)) + eret(); + return r.code; +} diff --git a/src/lib/kadm5/clnt/client_rpc.c b/src/lib/kadm5/clnt/client_rpc.c index c8d844e4c..6dd56872a 100644 --- a/src/lib/kadm5/clnt/client_rpc.c +++ b/src/lib/kadm5/clnt/client_rpc.c @@ -212,3 +212,11 @@ get_principal_keys_2(getpkeys_arg *argp, getpkeys_ret *res, CLIENT *clnt) (xdrproc_t)xdr_getpkeys_arg, (caddr_t)argp, (xdrproc_t)xdr_getpkeys_ret, (caddr_t)res, TIMEOUT); } + +enum clnt_stat +create_alias_2(calias_arg *argp, generic_ret *res, CLIENT *clnt) +{ + return clnt_call(clnt, CREATE_ALIAS, + (xdrproc_t)xdr_calias_arg, (caddr_t)argp, + (xdrproc_t)xdr_generic_ret, (caddr_t)res, TIMEOUT); +} diff --git a/src/lib/kadm5/clnt/libkadm5clnt_mit.exports b/src/lib/kadm5/clnt/libkadm5clnt_mit.exports index 9ed7d52dc..43b81e8ae 100644 --- a/src/lib/kadm5/clnt/libkadm5clnt_mit.exports +++ b/src/lib/kadm5/clnt/libkadm5clnt_mit.exports @@ -3,6 +3,7 @@ _kadm5_chpass_principal_util kadm5_chpass_principal kadm5_chpass_principal_3 kadm5_chpass_principal_util +kadm5_create_alias kadm5_create_policy kadm5_create_principal kadm5_create_principal_3 @@ -62,6 +63,7 @@ krb5_klog_reopen krb5_klog_set_context krb5_klog_syslog krb5_string_to_keysalts +xdr_calias_arg xdr_chpass3_arg xdr_chpass_arg xdr_chrand3_arg diff --git a/src/lib/kadm5/kadm_err.et b/src/lib/kadm5/kadm_err.et index cf07e8068..f58ddf6d7 100644 --- a/src/lib/kadm5/kadm_err.et +++ b/src/lib/kadm5/kadm_err.et @@ -67,4 +67,5 @@ error_code KADM5_SETKEY_BAD_KVNO, "Invalid multiple or duplicate kvnos in setkey error_code KADM5_AUTH_EXTRACT, "Operation requires ``extract-keys'' privilege" error_code KADM5_PROTECT_KEYS, "Principal keys are locked down" error_code KADM5_AUTH_INITIAL, "Operation requires initial ticket" +error_code KADM5_ALIAS_REALM, "Alias target must be within the same realm" end diff --git a/src/lib/kadm5/kadm_rpc.h b/src/lib/kadm5/kadm_rpc.h index 9efe49a37..bde85478a 100644 --- a/src/lib/kadm5/kadm_rpc.h +++ b/src/lib/kadm5/kadm_rpc.h @@ -246,6 +246,13 @@ struct getpkeys_ret { }; typedef struct getpkeys_ret getpkeys_ret; +struct calias_arg { + krb5_ui_4 api_version; + krb5_principal alias; + krb5_principal target; +}; +typedef struct calias_arg calias_arg; + #define KADM 2112 #define KADMVERS 2 #define CREATE_PRINCIPAL 1 @@ -360,4 +367,9 @@ extern enum clnt_stat get_principal_keys_2(getpkeys_arg *, getpkeys_ret *, CLIENT *); extern bool_t get_principal_keys_2_svc(getpkeys_arg *, getpkeys_ret *, struct svc_req *); + +#define CREATE_ALIAS 27 +extern enum clnt_stat create_alias_2(calias_arg *, generic_ret *, CLIENT *); +extern bool_t create_alias_2_svc(calias_arg *, generic_ret *, + struct svc_req *); #endif /* __KADM_RPC_H__ */ diff --git a/src/lib/kadm5/kadm_rpc_xdr.c b/src/lib/kadm5/kadm_rpc_xdr.c index 5e052dd90..8a2def8ff 100644 --- a/src/lib/kadm5/kadm_rpc_xdr.c +++ b/src/lib/kadm5/kadm_rpc_xdr.c @@ -1209,3 +1209,18 @@ xdr_getpkeys_ret(XDR *xdrs, getpkeys_ret *objp) } return TRUE; } + +bool_t +xdr_calias_arg(XDR *xdrs, calias_arg *objp) +{ + if (!xdr_ui_4(xdrs, &objp->api_version)) { + return (FALSE); + } + if (!xdr_krb5_principal(xdrs, &objp->alias)) { + return (FALSE); + } + if (!xdr_krb5_principal(xdrs, &objp->target)) { + return (FALSE); + } + return (TRUE); +} diff --git a/src/lib/kadm5/server_internal.h b/src/lib/kadm5/server_internal.h index 433f4915b..ce2e9197b 100644 --- a/src/lib/kadm5/server_internal.h +++ b/src/lib/kadm5/server_internal.h @@ -264,6 +264,13 @@ k5_kadm5_hook_rename (krb5_context context, int stage, krb5_principal oprinc, krb5_principal nprinc); +/** Call alias kadm5_hook entry point. */ +kadm5_ret_t +k5_kadm5_hook_alias (krb5_context context, + kadm5_hook_handle *handles, + int stage, + krb5_principal alias, krb5_principal target); + /** @}*/ #endif /* __KADM5_SERVER_INTERNAL_H__ */ diff --git a/src/lib/kadm5/srv/kadm5_hook.c b/src/lib/kadm5/srv/kadm5_hook.c index df337bc32..c533d743a 100644 --- a/src/lib/kadm5/srv/kadm5_hook.c +++ b/src/lib/kadm5/srv/kadm5_hook.c @@ -64,7 +64,7 @@ k5_kadm5_hook_load(krb5_context context, handle = k5alloc(sizeof(*handle), &ret); if (handle == NULL) goto cleanup; - ret = (*mod)(context, 1, 2, (krb5_plugin_vtable)&handle->vt); + ret = (*mod)(context, 1, 3, (krb5_plugin_vtable)&handle->vt); if (ret != 0) { /* Failed vtable init is non-fatal. */ free(handle); handle = NULL; @@ -184,3 +184,11 @@ k5_kadm5_hook_remove(krb5_context context, kadm5_hook_handle *handles, ITERATE(remove, (context, h->data, stage, princ)); return 0; } + +kadm5_ret_t +k5_kadm5_hook_alias(krb5_context context, kadm5_hook_handle *handles, + int stage, krb5_principal alias, krb5_principal target) +{ + ITERATE(alias, (context, h->data, stage, alias, target)); + return 0; +} diff --git a/src/lib/kadm5/srv/libkadm5srv_mit.exports b/src/lib/kadm5/srv/libkadm5srv_mit.exports index 14c02a7f1..3b39ab590 100644 --- a/src/lib/kadm5/srv/libkadm5srv_mit.exports +++ b/src/lib/kadm5/srv/libkadm5srv_mit.exports @@ -4,6 +4,7 @@ hist_princ kadm5_chpass_principal kadm5_chpass_principal_3 kadm5_chpass_principal_util +kadm5_create_alias kadm5_create_policy kadm5_create_principal kadm5_create_principal_3 @@ -74,6 +75,7 @@ master_db master_princ osa_free_princ_ent passwd_check +xdr_calias_arg xdr_chpass3_arg xdr_chpass_arg xdr_chrand3_arg diff --git a/src/lib/kadm5/srv/server_kdb.c b/src/lib/kadm5/srv/server_kdb.c index 4efcaf994..709e7f5d3 100644 --- a/src/lib/kadm5/srv/server_kdb.c +++ b/src/lib/kadm5/srv/server_kdb.c @@ -410,9 +410,7 @@ kdb_delete_entry(kadm5_server_handle_t handle, krb5_principal name) krb5_error_code ret; ret = krb5_db_delete_principal(handle->context, name); - if (ret == KRB5_KDB_NOENTRY) - ret = 0; - return ret; + return (ret == KRB5_KDB_NOENTRY) ? KADM5_UNK_PRINC : ret; } typedef struct _iter_data { diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c index 444c16ed7..1557937f2 100644 --- a/src/lib/kadm5/srv/svr_principal.c +++ b/src/lib/kadm5/srv/svr_principal.c @@ -520,8 +520,6 @@ kadm5_ret_t kadm5_delete_principal(void *server_handle, krb5_principal principal) { unsigned int ret; - krb5_db_entry *kdb; - osa_princ_ent_rec adb; kadm5_server_handle_t handle = server_handle; CHECK_HANDLE(server_handle); @@ -535,19 +533,13 @@ kadm5_delete_principal(void *server_handle, krb5_principal principal) if (krb5_principal_compare(handle->context, principal, master_princ)) return KADM5_PROTECT_PRINCIPAL; - if ((ret = kdb_get_entry(handle, principal, &kdb, &adb))) - return(ret); ret = k5_kadm5_hook_remove(handle->context, handle->hook_handles, KADM5_HOOK_STAGE_PRECOMMIT, principal); - if (ret) { - kdb_free_entry(handle, kdb, &adb); + if (ret) return ret; - } ret = kdb_delete_entry(handle, principal); - kdb_free_entry(handle, kdb, &adb); - if (ret == 0) (void) k5_kadm5_hook_remove(handle->context, handle->hook_handles, @@ -2056,3 +2048,42 @@ done: kdb_free_entry(handle, kdb, &adb); return ret; } + +kadm5_ret_t +kadm5_create_alias(void *server_handle, krb5_principal alias, + krb5_principal target) +{ + krb5_db_entry *kdb; + osa_princ_ent_rec adb = { 0 }; + krb5_error_code ret; + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + if (alias == NULL || target == NULL) + return EINVAL; + if (!krb5_realm_compare(handle->context, alias, target)) + return KADM5_ALIAS_REALM; + + ret = kdb_get_entry(handle, alias, &kdb, NULL); + if (!ret) { + kdb_free_entry(handle, kdb, NULL); + return KADM5_DUP; + } + + ret = k5_kadm5_hook_alias(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_PRECOMMIT, alias, target); + if (ret) + return ret; + + ret = krb5_dbe_make_alias_entry(handle->context, alias, target, &kdb); + if (ret) + return ret; + ret = kdb_put_entry(handle, kdb, &adb); + krb5_db_free_principal(handle->context, kdb); + if (ret) + return ret; + + (void) k5_kadm5_hook_alias(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_POSTCOMMIT, alias, target); + return 0; +} diff --git a/src/lib/kdb/kdb5.c b/src/lib/kdb/kdb5.c index a0897f5e8..b6db28d8d 100644 --- a/src/lib/kdb/kdb5.c +++ b/src/lib/kdb/kdb5.c @@ -797,27 +797,49 @@ krb5_db_unlock(krb5_context kcontext) return v->unlock(kcontext); } +#define MAX_ALIAS_DEPTH 10 + krb5_error_code krb5_db_get_principal(krb5_context kcontext, krb5_const_principal search_for, - unsigned int flags, krb5_db_entry **entry) + unsigned int flags, krb5_db_entry **entry_out) { krb5_error_code status = 0; kdb_vftabl *v; + krb5_db_entry *entry; + krb5_principal alias_target; + int alias_depth = 0; - *entry = NULL; + *entry_out = NULL; status = get_vftabl(kcontext, &v); if (status) return status; if (v->get_principal == NULL) return KRB5_PLUGIN_OP_NOTSUPP; - status = v->get_principal(kcontext, search_for, flags, entry); + + status = v->get_principal(kcontext, search_for, flags, &entry); if (status) return status; + /* Resolve any aliases up to the maximum depth. */ + for (;;) { + status = krb5_dbe_read_alias(kcontext, entry, &alias_target); + if (status) + return status; + if (alias_target == NULL) + break; + krb5_db_free_principal(kcontext, entry); + status = (++alias_depth > MAX_ALIAS_DEPTH) ? KRB5_KDB_NOENTRY : + v->get_principal(kcontext, alias_target, flags, &entry); + krb5_free_principal(kcontext, alias_target); + if (status) + return status; + } + /* Sort the keys in the db entry as some parts of krb5 expect it to be. */ - if ((*entry)->key_data != NULL) - krb5_dbe_sort_key_data((*entry)->key_data, (*entry)->n_key_data); + if (entry->key_data != NULL) + krb5_dbe_sort_key_data(entry->key_data, entry->n_key_data); + *entry_out = entry; return 0; } @@ -1036,6 +1058,7 @@ krb5_db_rename_principal(krb5_context kcontext, krb5_principal source, kdb_vftabl *v; krb5_error_code status; krb5_db_entry *entry; + krb5_boolean eq; status = get_vftabl(kcontext, &v); if (status) @@ -1050,6 +1073,15 @@ krb5_db_rename_principal(krb5_context kcontext, krb5_principal source, logging(kcontext)) return KRB5_PLUGIN_OP_NOTSUPP; + /* Disallow the operation if source is an alias. */ + status = krb5_db_get_principal(kcontext, source, 0, &entry); + if (status) + return status; + eq = krb5_principal_compare(kcontext, entry->princ, source); + krb5_db_free_principal(kcontext, entry); + if (!eq) + return KRB5_KDB_ALIAS_UNSUPPORTED; + status = krb5_db_get_principal(kcontext, target, 0, &entry); if (status == 0) { krb5_db_free_principal(kcontext, entry); @@ -2789,3 +2821,73 @@ krb5_db_issue_pac(krb5_context context, unsigned int flags, return v->issue_pac(context, flags, client, replaced_reply_key, server, krbtgt, authtime, old_pac, new_pac, auth_indicators); } + +krb5_error_code +krb5_dbe_make_alias_entry(krb5_context context, krb5_const_principal alias, + krb5_const_principal target, krb5_db_entry **out) +{ + krb5_error_code ret; + krb5_principal princ = NULL; + char *target_str = NULL; + krb5_tl_data *tl = NULL; + krb5_db_entry *ent; + + *out = NULL; + + ret = krb5_copy_principal(context, alias, &princ); + if (ret) + goto cleanup; + + ret = krb5_unparse_name(context, target, &target_str); + if (ret) + goto cleanup; + tl = k5alloc(sizeof(*tl), &ret); + if (tl == NULL) + goto cleanup; + tl->tl_data_next = NULL; + tl->tl_data_type = KRB5_TL_ALIAS_TARGET; + tl->tl_data_length = strlen(target_str) + 1; + tl->tl_data_contents = (uint8_t *)target_str; + + ent = k5alloc(sizeof(*ent), &ret); + if (ent == NULL) + goto cleanup; + ent->len = KRB5_KDB_V1_BASE_LENGTH; + ent->attributes = KRB5_KDB_DISALLOW_ALL_TIX; + ent->princ = princ; + ent->tl_data = tl; + ent->n_tl_data = 1; + princ = NULL; + target_str = NULL; + tl = NULL; + *out = ent; + +cleanup: + krb5_free_principal(context, princ); + krb5_free_unparsed_name(context, target_str); + free(tl); + return ret; +} + +krb5_error_code +krb5_dbe_read_alias(krb5_context context, krb5_db_entry *entry, + krb5_principal *target_out) +{ + krb5_error_code ret; + krb5_tl_data tl; + + *target_out = NULL; + + tl.tl_data_type = KRB5_TL_ALIAS_TARGET; + ret = krb5_dbe_lookup_tl_data(context, entry, &tl); + if (ret) + return ret; + + if (tl.tl_data_length == 0) + return 0; + + if (tl.tl_data_contents[tl.tl_data_length - 1] != '\0') + return KRB5_KDB_TRUNCATED_RECORD; + + return krb5_parse_name(context, (char *)tl.tl_data_contents, target_out); +} diff --git a/src/lib/kdb/libkdb5.exports b/src/lib/kdb/libkdb5.exports index 574bab92f..11cd0bdd1 100644 --- a/src/lib/kdb/libkdb5.exports +++ b/src/lib/kdb/libkdb5.exports @@ -107,3 +107,5 @@ ulog_replay ulog_set_last xdr_kdb_incr_update_t krb5_dbe_sort_key_data +krb5_dbe_make_alias_entry +krb5_dbe_read_alias diff --git a/src/lib/krb5/error_tables/kdb5_err.et b/src/lib/krb5/error_tables/kdb5_err.et index 1b08ec1a6..f803df1d3 100644 --- a/src/lib/krb5/error_tables/kdb5_err.et +++ b/src/lib/krb5/error_tables/kdb5_err.et @@ -85,5 +85,6 @@ ec KRB5_LOG_ERROR, "Generic update log error" ec KRB5_KDB_DBTYPE_MISMATCH, "Database module does not match KDC version" ec KRB5_KDB_POLICY_REF, "Policy is in use" ec KRB5_KDB_STRINGS_TOOLONG, "Too much string mapping data" +ec KRB5_KDB_ALIAS_UNSUPPORTED, "Operation unsupported on alias principal name" end diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c index 4ff5219c2..234a2b53b 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c @@ -128,6 +128,76 @@ krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry) return; } +static krb5_error_code +iterate_entry(krb5_context context, krb5_ldap_context *ldap_context, + LDAP *ld, LDAPMessage *ent, + krb5_error_code (*func)(krb5_pointer, krb5_db_entry *), + krb5_pointer func_arg) +{ + krb5_error_code ret = 0; + krb5_principal cprinc = NULL, nprinc = NULL; + krb5_db_entry entry = { 0 }, *entptr; + char **canon = NULL, **names = NULL, **list; + size_t i; + + canon = ldap_get_values(ld, ent, "krbCanonicalName"); + names = ldap_get_values(ld, ent, "krbPrincipalName"); + if (canon == NULL && names == NULL) + return 0; + + /* Output an entry for the canonical name if one is given. Otherwise + * output an entry for the first name within the realm. */ + list = (canon != NULL) ? canon : names; + for (i = 0; list[i] != NULL; i++) { + krb5_free_principal(context, nprinc); + nprinc = NULL; + ret = krb5_ldap_parse_name(context, list[i], &nprinc); + if (ret) + goto cleanup; + + if (is_principal_in_realm(ldap_context, nprinc)) { + ret = populate_krb5_db_entry(context, ldap_context, ld, ent, + nprinc, &entry); + if (ret) + goto cleanup; + ret = (*func)(func_arg, &entry); + krb5_dbe_free_contents(context, &entry); + if (ret) + goto cleanup; + break; + } + } + + /* Output alias entries for each non-canonical name. */ + if (canon != NULL && names != NULL) { + ret = krb5_ldap_parse_name(context, canon[0], &cprinc); + if (ret) + goto cleanup; + for (i = 0; names[i] != NULL; i++) { + if (strcmp(names[i], canon[0]) == 0) + continue; + krb5_free_principal(context, nprinc); + nprinc = NULL; + ret = krb5_ldap_parse_name(context, names[i], &nprinc); + if (ret) + goto cleanup; + ret = krb5_dbe_make_alias_entry(context, nprinc, cprinc, &entptr); + if (ret) + goto cleanup; + ret = (*func)(func_arg, entptr); + krb5_db_free_principal(context, entptr); + if (ret) + goto cleanup; + } + } + +cleanup: + krb5_free_principal(context, cprinc); + krb5_free_principal(context, nprinc); + ldap_value_free(canon); + ldap_value_free(names); + return ret; +} krb5_error_code krb5_ldap_iterate(krb5_context context, char *match_expr, @@ -135,9 +205,8 @@ krb5_ldap_iterate(krb5_context context, char *match_expr, krb5_pointer func_arg, krb5_flags iterflags) { krb5_db_entry entry; - krb5_principal principal; - char **subtree=NULL, *princ_name=NULL, *realm=NULL, **values=NULL, *filter=NULL; - size_t tree=0, ntree=1, i=0; + char **subtree=NULL, *realm=NULL, *filter=NULL; + size_t tree=0, ntree=1; krb5_error_code st=0, tempst=0; LDAP *ld=NULL; LDAPMessage *result=NULL, *ent=NULL; @@ -181,33 +250,10 @@ krb5_ldap_iterate(krb5_context context, char *match_expr, LDAP_SEARCH(subtree[tree], ldap_context->lrparams->search_scope, filter, principal_attributes); for (ent=ldap_first_entry(ld, result); ent != NULL; ent=ldap_next_entry(ld, ent)) { - values=ldap_get_values(ld, ent, "krbcanonicalname"); - if (values == NULL) - values=ldap_get_values(ld, ent, "krbprincipalname"); - if (values != NULL) { - for (i=0; values[i] != NULL; ++i) { - if (krb5_ldap_parse_principal_name(values[i], &princ_name) != 0) - continue; - st = krb5_parse_name(context, princ_name, &principal); - free(princ_name); - if (st) - continue; - - if (is_principal_in_realm(ldap_context, principal)) { - st = populate_krb5_db_entry(context, ldap_context, ld, - ent, principal, &entry); - krb5_free_principal(context, principal); - if (st) - goto cleanup; - (*func)(func_arg, &entry); - krb5_dbe_free_contents(context, &entry); - break; - } - (void) krb5_free_principal(context, principal); - } - ldap_value_free(values); - } - } /* end of for (ent= ... */ + st = iterate_entry(context, ldap_context, ld, ent, func, func_arg); + if (st) + goto cleanup; + } ldap_msgfree(result); result = NULL; } /* end of for (tree= ... */ @@ -268,15 +314,17 @@ krb5_ldap_delete_principal(krb5_context context, GET_HANDLE(); - if (ptype == KDB_STANDALONE_PRINCIPAL_OBJECT) { + if (ptype == KDB_STANDALONE_PRINCIPAL_OBJECT && + (pcount == 1 || + krb5_principal_compare(context, searchfor, entry->princ))) { st = ldap_delete_ext_s(ld, DN, NULL, NULL); if (st != LDAP_SUCCESS) { st = set_ldap_error (context, st, OP_DEL); goto cleanup; } } else { - if (((st=krb5_unparse_name(context, searchfor, &user)) != 0) - || ((st=krb5_ldap_unparse_principal_name(user)) != 0)) + st = krb5_ldap_unparse_name(context, searchfor, &user); + if (st) goto cleanup; memset(strval, 0, sizeof(strval)); @@ -362,35 +410,6 @@ is_standalone_principal(krb5_context kcontext, krb5_db_entry *entry, int *res) return code; } -/* - * Unparse princ in the format used for LDAP attributes, and set *user to the - * result. - */ -static krb5_error_code -unparse_principal_name(krb5_context context, krb5_const_principal princ, - char **user_out) -{ - krb5_error_code st; - char *luser = NULL; - - *user_out = NULL; - - st = krb5_unparse_name(context, princ, &luser); - if (st) - goto cleanup; - - st = krb5_ldap_unparse_principal_name(luser); - if (st) - goto cleanup; - - *user_out = luser; - luser = NULL; - -cleanup: - free(luser); - return st; -} - /* * Rename a principal's rdn. * @@ -478,10 +497,10 @@ krb5_ldap_rename_principal(krb5_context context, krb5_const_principal source, goto cleanup; } - st = unparse_principal_name(context, source, &suser); + st = krb5_ldap_unparse_name(context, source, &suser); if (st) goto cleanup; - st = unparse_principal_name(context, target, &tuser); + st = krb5_ldap_unparse_name(context, target, &tuser); if (st) goto cleanup; @@ -565,65 +584,63 @@ cleanup: return st; } -/* - * Function: krb5_ldap_unparse_principal_name - * - * Purpose: Removes '\\' that comes before every occurrence of '@' - * in the principal name component. - * - * Arguments: - * user_name (input/output) Principal name - * - */ - +/* Unparse princ in the format used for krb5 principal names within LDAP + * attributes. */ krb5_error_code -krb5_ldap_unparse_principal_name(char *user_name) +krb5_ldap_unparse_name(krb5_context context, krb5_const_principal princ, + char **user_out) { - char *in, *out; + krb5_error_code ret; + char *p, *q; + + ret = krb5_unparse_name(context, princ, user_out); + if (ret) + return ret; - out = user_name; - for (in = user_name; *in; in++) { - if (*in == '\\' && *(in + 1) == '@') + /* Remove backslashes preceding at-signs in the unparsed string. */ + for (q = p = *user_out; *p != '\0'; p++) { + if (*p == '\\' && *(p + 1) == '@') continue; - *out++ = *in; + *q++ = *p; } - *out = '\0'; + *q = '\0'; return 0; } - -/* - * Function: krb5_ldap_parse_principal_name - * - * Purpose: Inserts '\\' before every occurrence of '@' - * in the principal name component. - * - * Arguments: - * i_princ_name (input) Principal name without '\\' - * o_princ_name (output) Principal name with '\\' - * - * Note: The caller has to free the memory allocated for o_princ_name. - */ - +/* Parse username in the format used for krb5 principal names within LDAP + * attributes. */ krb5_error_code -krb5_ldap_parse_principal_name(char *i_princ_name, char **o_princ_name) +krb5_ldap_parse_name(krb5_context context, const char *username, + krb5_principal *out) { - const char *at_rlm_name, *p; + krb5_error_code ret; + const char *at_realm, *p; + char *princstr; struct k5buf buf; - at_rlm_name = strrchr(i_princ_name, '@'); - if (!at_rlm_name) { - *o_princ_name = strdup(i_princ_name); + *out = NULL; + + /* Make a copy of username, inserting a backslash before each '@' + * before the last one. */ + at_realm = strrchr(username, '@'); + if (at_realm == NULL) { + princstr = strdup(username); } else { k5_buf_init_dynamic(&buf); - for (p = i_princ_name; p < at_rlm_name; p++) { + for (p = username; p < at_realm; p++) { if (*p == '@') k5_buf_add(&buf, "\\"); k5_buf_add_len(&buf, p, 1); } - k5_buf_add(&buf, at_rlm_name); - *o_princ_name = k5_buf_cstring(&buf); + k5_buf_add(&buf, at_realm); + princstr = k5_buf_cstring(&buf); } - return (*o_princ_name == NULL) ? ENOMEM : 0; + + if (princstr == NULL) + return ENOMEM; + + ret = krb5_parse_name(context, princstr, out); + free(princstr); + return ret; } diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h index 72a9f960b..71948aac3 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h @@ -124,10 +124,12 @@ void krb5_dbe_free_contents(krb5_context, krb5_db_entry *); krb5_error_code -krb5_ldap_unparse_principal_name(char *); +krb5_ldap_unparse_name(krb5_context context, krb5_const_principal princ, + char **user_out); krb5_error_code -krb5_ldap_parse_principal_name(char *, char **); +krb5_ldap_parse_name(krb5_context context, const char *username, + krb5_principal *out); struct berval** krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data, diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c index d929d325c..ae4e03f8c 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c @@ -98,6 +98,110 @@ berval2tl_data(struct berval *in, krb5_tl_data **out) return 0; } +/* + * Search for filter at the specified base and scope, expecting to find a + * single result. Set *entry_out to an object containing all principal + * attributes. Set *result_out to the result message to be freed with + * ldap_msgfree(). Set both to NULL if no matching entry is found. + */ +static krb5_error_code +search_at(krb5_context context, krb5_ldap_context *ldap_context, + krb5_ldap_server_handle *ldap_server_handle, const char *base, + int scope, const char *filter, const char *user, + LDAPMessage **entry_out, LDAPMessage **result_out) +{ + krb5_error_code st, tempst; + LDAPMessage *result = NULL; + LDAP *ld = ldap_server_handle->ldap_handle; + int nentries; + + *entry_out = *result_out = NULL; + + LDAP_SEARCH(base, scope, filter, principal_attributes); + nentries = ldap_count_entries(ld, result); + if (nentries > 1) { + st = EINVAL; + k5_setmsg(context, st, + _("Operation cannot continue; more than one " + "entry with principal name \"%s\" found"), + user); + goto cleanup; + } + + if (nentries == 1) { + *result_out = result; + *entry_out = ldap_first_entry(ld, result); + return 0; + } + +cleanup: + ldap_msgfree(result); + return st; +} + +/* + * Search for an LDAP object matching princ, either at the specified dn with + * base scope, or, if dn is NULL, in all configured subtrees with the + * configured search scope (usually subtree). Set *entry_out and *result_out + * in the same way as search_at(). + */ +static krb5_error_code +search_princ(krb5_context context, krb5_ldap_context *ldap_context, + krb5_ldap_server_handle *ldap_server_handle, + krb5_const_principal princ, const char *dn, + LDAPMessage **entry_out, LDAPMessage **result_out) +{ + krb5_error_code st; + char *user = NULL, *filtuser = NULL, *filter = NULL; + char **subtreelist = NULL; + size_t ntrees = 0, i; + + *entry_out = *result_out = NULL; + + st = krb5_ldap_unparse_name(context, princ, &user); + if (st) + goto cleanup; + + filtuser = ldap_filter_correct(user); + if (filtuser == NULL) { + st = ENOMEM; + goto cleanup; + } + + if (asprintf(&filter, FILTER"%s))", filtuser) < 0) { + filter = NULL; + st = ENOMEM; + goto cleanup; + } + + if (dn != NULL) { + st = search_at(context, ldap_context, ldap_server_handle, dn, + LDAP_SCOPE_BASE, filter, user, entry_out, result_out); + goto cleanup; + } + + st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees); + if (st) + goto cleanup; + + for (i = 0; i < ntrees; i++) { + st = search_at(context, ldap_context, ldap_server_handle, + subtreelist[i], ldap_context->lrparams->search_scope, + filter, user, entry_out, result_out); + if (st || *entry_out != NULL) + goto cleanup; + } + +cleanup: + free(user); + free(filtuser); + free(filter); + while (ntrees > 0) + free(subtreelist[--ntrees]); + free(subtreelist); + return st; +} + /* * look up a principal in the directory. */ @@ -106,18 +210,15 @@ krb5_error_code krb5_ldap_get_principal(krb5_context context, krb5_const_principal searchfor, unsigned int flags, krb5_db_entry **entry_ptr) { - char *user=NULL, *filter=NULL, *filtuser=NULL; - size_t tree=0, ntrees=1, princlen=0; - krb5_error_code tempst=0, st=0; - char **values=NULL, **subtree=NULL, *cname=NULL; - LDAP *ld=NULL; - LDAPMessage *result=NULL, *ent=NULL; - krb5_ldap_context *ldap_context=NULL; - kdb5_dal_handle *dal_handle=NULL; - krb5_ldap_server_handle *ldap_server_handle=NULL; - krb5_principal cprinc=NULL; - krb5_boolean found=FALSE; - krb5_db_entry *entry = NULL; + krb5_error_code st; + char **values = NULL; + LDAP *ld = NULL; + LDAPMessage *ent = NULL, *result = NULL; + kdb5_dal_handle *dal_handle; + krb5_ldap_context *ldap_context; + krb5_ldap_server_handle *ldap_server_handle = NULL; + krb5_principal cprinc = NULL; + krb5_db_entry *entry = NULL; *entry_ptr = NULL; @@ -138,116 +239,42 @@ krb5_ldap_get_principal(krb5_context context, krb5_const_principal searchfor, goto cleanup; } - if ((st=krb5_unparse_name(context, searchfor, &user)) != 0) - goto cleanup; + GET_HANDLE(); - if ((st=krb5_ldap_unparse_principal_name(user)) != 0) + st = search_princ(context, ldap_context, ldap_server_handle, searchfor, + NULL, &ent, &result); + if (st) goto cleanup; - - filtuser = ldap_filter_correct(user); - if (filtuser == NULL) { - st = ENOMEM; + if (ent == NULL) { + st = KRB5_KDB_NOENTRY; goto cleanup; } - princlen = strlen(FILTER) + strlen(filtuser) + 2 + 1; /* 2 for closing brackets */ - if ((filter = malloc(princlen)) == NULL) { - st = ENOMEM; - goto cleanup; + /* Use the canonical principal name if one is present in the object. */ + values = ldap_get_values(ld, ent, "krbCanonicalName"); + if (values != NULL && values[0] != NULL) { + st = krb5_ldap_parse_name(context, values[0], &cprinc); + if (st != 0) + goto cleanup; } - snprintf(filter, princlen, FILTER"%s))", filtuser); + ldap_value_free(values); - if ((st = krb5_get_subtree_info(ldap_context, &subtree, &ntrees)) != 0) + entry = k5alloc(sizeof(*entry), &st); + if (entry == NULL) + goto cleanup; + st = populate_krb5_db_entry(context, ldap_context, ld, ent, + cprinc != NULL ? cprinc : searchfor, entry); + if (st) goto cleanup; - GET_HANDLE(); - for (tree=0; tree < ntrees && !found; ++tree) { - - LDAP_SEARCH(subtree[tree], ldap_context->lrparams->search_scope, filter, principal_attributes); - for (ent=ldap_first_entry(ld, result); ent != NULL && !found; ent=ldap_next_entry(ld, ent)) { - - /* get the associated directory user information */ - if ((values=ldap_get_values(ld, ent, "krbprincipalname")) != NULL) { - size_t i; - - /* a wild-card in a principal name can return a list of kerberos principals. - * Make sure that the correct principal is returned. - * NOTE: a principalname k* in ldap server will return all the principals starting with a k - */ - for (i=0; values[i] != NULL; ++i) { - if (strcmp(values[i], user) == 0) { - found = TRUE; - break; - } - } - ldap_value_free(values); - - if (!found) /* no matching principal found */ - continue; - } - - if ((values=ldap_get_values(ld, ent, "krbcanonicalname")) != NULL) { - if (values[0] && strcmp(values[0], user) != 0) { - /* We matched an alias, not the canonical name. */ - st = krb5_ldap_parse_principal_name(values[0], &cname); - if (st != 0) - goto cleanup; - st = krb5_parse_name(context, cname, &cprinc); - if (st != 0) - goto cleanup; - } - ldap_value_free(values); - if (!found) - continue; - } - - entry = k5alloc(sizeof(*entry), &st); - if (entry == NULL) - goto cleanup; - if ((st = populate_krb5_db_entry(context, ldap_context, ld, ent, - cprinc ? cprinc : searchfor, - entry)) != 0) - goto cleanup; - } - ldap_msgfree(result); - result = NULL; - } /* for (tree=0 ... */ - - if (found) { - *entry_ptr = entry; - entry = NULL; - } else - st = KRB5_KDB_NOENTRY; + *entry_ptr = entry; + entry = NULL; cleanup: ldap_msgfree(result); krb5_db_free_principal(context, entry); - - if (filter) - free (filter); - - if (subtree) { - for (; ntrees; --ntrees) - if (subtree[ntrees-1]) - free (subtree[ntrees-1]); - free (subtree); - } - - if (ldap_server_handle) - krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle); - - if (user) - free(user); - - if (filtuser) - free(filtuser); - - if (cname) - free(cname); - - if (cprinc) - krb5_free_principal(context, cprinc); - + krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle); + krb5_free_principal(context, cprinc); return st; } @@ -755,13 +782,93 @@ validate_xargs(krb5_context context, return 0; } +static krb5_error_code +add_alias(krb5_context context, krb5_ldap_context *ldap_context, + krb5_ldap_server_handle *ldap_server_handle, + krb5_const_principal alias, krb5_const_principal target) +{ + krb5_error_code st; + LDAP *ld = ldap_server_handle->ldap_handle; + LDAPMessage *ent, *result = NULL; + LDAPMod **mods = NULL; + char **canon = NULL, **names = NULL, *user = NULL, *dn = NULL; + char *strval[2] = { NULL }, errbuf[1024]; + + st = search_princ(context, ldap_context, ldap_server_handle, target, NULL, + &ent, &result); + if (st) + goto cleanup; + if (ent == NULL) { + st = KRB5_KDB_NOENTRY; + k5_setmsg(context, st, _("target principal not found")); + goto cleanup; + } + + dn = ldap_get_dn(ld, ent); + if (dn == NULL) { + ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &st); + st = set_ldap_error(context, st, 0); + goto cleanup; + } + canon = ldap_get_values(ld, ent, "krbCanonicalName"); + names = ldap_get_values(ld, ent, "krbPrincipalName"); + + /* Add a krbCanonicalName attribute if one isn't set. */ + if (canon == NULL) { + if (ldap_count_values(names) != 1) { + st = KRB5_KDB_INTERNAL_ERROR; + k5_setmsg(context, st, + _("cannot add alias to entry with multiple " + "krbPrincipalName values and no krbCanonicalName " + "attribute")); + goto cleanup; + } + strval[0] = names[0]; + st = krb5_add_str_mem_ldap_mod(&mods, "krbCanonicalName", LDAP_MOD_ADD, + strval); + if (st) + goto cleanup; + } + + /* Add a krbPrincipalName value for the alias name. */ + st = krb5_ldap_unparse_name(context, alias, &user); + if (st) + goto cleanup; + strval[0] = user; + st = krb5_add_str_mem_ldap_mod(&mods, "krbPrincipalName", LDAP_MOD_ADD, + strval); + if (st) + goto cleanup; + + st = ldap_modify_ext_s(ld, dn, mods, NULL, NULL); + if (st == LDAP_TYPE_OR_VALUE_EXISTS) { + st = KRB5_KDB_INUSE; + goto cleanup; + } else if (st != LDAP_SUCCESS) { + snprintf(errbuf, sizeof(errbuf), _("Alias modification failed: %s"), + ldap_err2string(st)); + st = translate_ldap_error(st, OP_MOD); + k5_setmsg(context, st, "%s", errbuf); + goto cleanup; + } + +cleanup: + ldap_msgfree(result); + ldap_memfree(dn); + ldap_value_free(canon); + ldap_value_free(names); + krb5_free_unparsed_name(context, user); + ldap_mods_free(mods, 1); + return st; +} + krb5_error_code krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, char **db_args) { int kerberos_principal_object_type=0; size_t l=0, ntrees=0, tre=0; - krb5_error_code st=0, tempst=0; + krb5_error_code st=0; LDAP *ld=NULL; LDAPMessage *result=NULL, *ent=NULL; char **subtreelist = NULL; @@ -778,6 +885,7 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, kdb5_dal_handle *dal_handle=NULL; krb5_ldap_context *ldap_context=NULL; krb5_ldap_server_handle *ldap_server_handle=NULL; + krb5_principal alias_target=NULL; osa_princ_ent_rec princ_ent = {0}; xargs_t xargs = {0}; char *polname = NULL; @@ -802,9 +910,20 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, goto cleanup; } + /* If this is an alias entry, add an alias to the target and return. */ + st = krb5_dbe_read_alias(context, entry, &alias_target); + if (st) + goto cleanup; + if (alias_target != NULL) { + st = add_alias(context, ldap_context, ldap_server_handle, entry->princ, + alias_target); + krb5_free_principal(context, alias_target); + goto cleanup; + } + /* get the principal information to act on */ - if (((st=krb5_unparse_name(context, entry->princ, &user)) != 0) || - ((st=krb5_ldap_unparse_principal_name(user)) != 0)) + st = krb5_ldap_unparse_name(context, entry->princ, &user); + if (st) goto cleanup; filtuser = ldap_filter_correct(user); if (filtuser == NULL) { @@ -830,72 +949,29 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, goto cleanup; if (entry->mask & KADM5_LOAD) { - size_t tree = 0; - int numlentries = 0; - - /* A load operation is special, will do a mix-in (add krbprinc - * attrs to a non-krb object entry) if an object exists with a - * matching krbprincipalname attribute so try to find existing - * object and set principal_dn. This assumes that the - * krbprincipalname attribute is unique (only one object entry has - * a particular krbprincipalname attribute). + /* + * A load operation is special, will do a mix-in (add krbprinc attrs to + * a non-krb object entry) if an object exists with a matching + * krbprincipalname attribute so try to find existing object and set + * principal_dn. This assumes that the krbprincipalname attribute is + * unique (only one object entry has a particular krbprincipalname + * attribute). */ - if (asprintf(&filter, FILTER"%s))", filtuser) < 0) { - filter = NULL; - st = ENOMEM; - goto cleanup; - } - - /* get the current subtree list */ - if ((st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees)) != 0) + st = search_princ(context, ldap_context, ldap_server_handle, + entry->princ, principal_dn, &ent, &result); + if (st && st != KRB5_KDB_NOENTRY) goto cleanup; - - found_entry = FALSE; - /* search for entry with matching krbprincipalname attribute */ - for (tree = 0; found_entry == FALSE && tree < ntrees; ++tree) { + if (ent != NULL && principal_dn == NULL) { + /* Remember this DN to be modified later. */ + principal_dn = ldap_get_dn(ld, ent); if (principal_dn == NULL) { - LDAP_SEARCH_1(subtreelist[tree], ldap_context->lrparams->search_scope, filter, principal_attributes, IGNORE_STATUS); - } else { - /* just look for entry with principal_dn */ - LDAP_SEARCH_1(principal_dn, LDAP_SCOPE_BASE, filter, principal_attributes, IGNORE_STATUS); - } - if (st == LDAP_SUCCESS) { - numlentries = ldap_count_entries(ld, result); - if (numlentries > 1) { - st = EINVAL; - k5_setmsg(context, st, - _("operation can not continue, more than one " - "entry with principal name \"%s\" found"), - user); - goto cleanup; - } else if (numlentries == 1) { - found_entry = TRUE; - if (principal_dn == NULL) { - ent = ldap_first_entry(ld, result); - if (ent != NULL) { - /* setting principal_dn will cause that entry to be modified further down */ - if ((principal_dn = ldap_get_dn(ld, ent)) == NULL) { - ldap_get_option (ld, LDAP_OPT_RESULT_CODE, &st); - st = set_ldap_error (context, st, 0); - goto cleanup; - } - } - } - } - } else if (st != LDAP_NO_SUCH_OBJECT) { - /* could not perform search, return with failure */ - st = set_ldap_error (context, st, 0); + ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &st); + st = set_ldap_error(context, st, 0); goto cleanup; } - ldap_msgfree(result); - result = NULL; - /* - * If it isn't found then assume a standalone princ entry is to - * be created. - */ - } /* end for (tree = 0; principal_dn == ... */ + } - if (found_entry == FALSE && principal_dn != NULL) { + if (ent == NULL && principal_dn != NULL) { /* * if principal_dn is null then there is code further down to * deal with setting standalone_principal_dn. Also note that @@ -959,12 +1035,9 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, * any of the subtrees */ if (xargs.dn_from_kbd == TRUE) { - /* Get the current subtree list if we haven't already done so. */ - if (subtreelist == NULL) { - st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees); - if (st) - goto cleanup; - } + st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees); + if (st) + goto cleanup; st = validate_xargs(context, ldap_server_handle, &xargs, standalone_principal_dn, subtreelist, ntrees); diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in index 1c69dc7f9..41ac0d3b2 100644 --- a/src/tests/Makefile.in +++ b/src/tests/Makefile.in @@ -192,6 +192,7 @@ check-pytests: responder s2p s4u2proxy unlockiter s4u2self $(RUNPYTEST) $(srcdir)/t_kdcoptions.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_replay.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_sendto_kdc.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_alias.py $(PYTESTFLAGS) clean: $(RM) adata conccache etinfo forward gcred hist hooks hrealm diff --git a/src/tests/t_alias.py b/src/tests/t_alias.py new file mode 100755 index 000000000..f52163241 --- /dev/null +++ b/src/tests/t_alias.py @@ -0,0 +1,124 @@ +from k5test import * + +realm = K5Realm(create_host=False) + +mark('getprinc') +realm.addprinc('canon') +realm.run([kadminl, 'alias', 'alias', 'canon at KRBTEST.COM']) +realm.run([kadminl, 'getprinc', 'alias'], + expected_msg='Principal: canon at KRBTEST.COM') + +mark('delprinc') +realm.run([kadminl, 'delprinc', 'alias']) +realm.run([kadminl, 'getprinc', 'alias'], expected_code=1, + expected_msg='does not exist') +realm.run([kadminl, 'getprinc', 'canon'], expected_msg=': canon at KRBTEST.COM') + +mark('no specified realm') +realm.run([kadminl, 'alias', 'alias', 'canon']) +realm.run([kadminl, 'getprinc', 'alias'], expected_msg=': canon at KRBTEST.COM') + +mark('cross-realm') +realm.run([kadminl, 'alias', 'x', 'y at OTHER.REALM'], expected_code=1, + expected_msg='Alias target must be within the same realm') + +mark('alias as service principal') +realm.extract_keytab('alias', realm.keytab) +realm.run([kvno, 'alias']) +realm.klist('user at KRBTEST.COM', 'alias at KRBTEST.COM') + +mark('alias as client principal') +realm.kinit('alias', flags=['-k']) +realm.klist('alias at KRBTEST.COM') +realm.kinit('alias', flags=['-k', '-C']) +realm.klist('canon at KRBTEST.COM') + +mark('chain') +realm.run([kadminl, 'alias', 'a1', 'canon']) +realm.run([kadminl, 'alias', 'a2', 'a1']) +realm.run([kadminl, 'alias', 'a3', 'a2']) +realm.run([kadminl, 'alias', 'a4', 'a3']) +realm.run([kadminl, 'alias', 'a5', 'a4']) +realm.run([kadminl, 'alias', 'a6', 'a5']) +realm.run([kadminl, 'alias', 'a7', 'a6']) +realm.run([kadminl, 'alias', 'a8', 'a7']) +realm.run([kadminl, 'alias', 'a9', 'a8']) +realm.run([kadminl, 'alias', 'a10', 'a9']) +realm.run([kadminl, 'alias', 'a11', 'a10']) +realm.run([kvno, 'a1']) +realm.run([kvno, 'a2']) +realm.run([kvno, 'a3']) +realm.run([kvno, 'a4']) +realm.run([kvno, 'a5']) +realm.run([kvno, 'a6']) +realm.run([kvno, 'a7']) +realm.run([kvno, 'a8']) +realm.run([kvno, 'a9']) +realm.run([kvno, 'a10']) +realm.run([kvno, 'a11'], expected_code=1, + expected_msg='Server a11 at KRBTEST.COM not found in Kerberos database') + +mark('circular chain') +realm.run([kadminl, 'alias', 'selfalias', 'selfalias']) +realm.run([kvno, 'selfalias'], expected_code=1, + expected_msg='Server selfalias at KRBTEST.COM not found') + +mark('blocking creations') +realm.run([kadminl, 'addprinc', '-nokey', 'alias'], expected_code=1, + expected_msg='already exists') +realm.run([kadminl, 'alias', 'alias', 'canon'], expected_code=1, + expected_msg='already exists') +realm.run([kadminl, 'renprinc', 'user', 'alias'], expected_code=1, + expected_msg='already exists') + +# Non-resolving aliases being overwritable is emergent behavior; +# change the tests if the behavior changes. +mark('not blocking creations') +realm.run([kadminl, 'alias', 'xa1', 'x']) +realm.run([kadminl, 'alias', 'xa2', 'x']) +realm.run([kadminl, 'alias', 'xa3', 'x']) +realm.addprinc('xa1') +realm.run([kadminl, 'getprinc', 'xa1'], expected_msg=': xa1 at KRBTEST.COM') +realm.run([kadminl, 'alias', 'xa2', 'canon']) +realm.run([kadminl, 'getprinc', 'xa2'], expected_msg=': canon at KRBTEST.COM') +realm.run([kadminl, 'renprinc', 'xa1', 'xa3']) +realm.run([kadminl, 'getprinc', 'xa3'], expected_msg=': xa3 at KRBTEST.COM') + +mark('renprinc') +realm.run([kadminl, 'renprinc', 'alias', 'nalias'], expected_code=1, + expected_msg='Operation unsupported on alias principal name') + +mark('modprinc') +realm.run([kadminl, 'modprinc', '+preauth', 'alias']) +realm.run([kadminl, 'getprinc', 'canon'], expected_msg='REQUIRES_PRE_AUTH') + +mark('cpw') +realm.run([kadminl, 'cpw', '-pw', 'pw', 'alias']) +realm.run([kadminl, 'getprinc', 'canon'], expected_msg='vno 2,') +realm.run([kadminl, 'cpw', '-e', 'aes256-cts', '-pw', 'pw', 'alias']) +realm.run([kadminl, 'getprinc', 'canon'], expected_msg='vno 3,') +realm.run([kadminl, 'cpw', '-randkey', 'alias']) +realm.run([kadminl, 'getprinc', 'canon'], expected_msg='vno 4,') +realm.run([kadminl, 'cpw', '-e', 'aes256-cts', '-randkey', 'alias']) +realm.run([kadminl, 'getprinc', 'canon'], expected_msg='vno 5,') + +mark('listprincs') +realm.run([kadminl, 'listprincs'], expected_msg='alias at KRBTEST.COM') + +mark('purgekeys') +realm.run([kadminl, 'purgekeys', '-all', 'alias']) +realm.run([kadminl, 'getprinc', 'canon'], expected_msg='Number of keys: 0') + +mark('setstr') +realm.run([kadminl, 'setstr', 'alias', 'key', 'value']) +realm.run([kadminl, 'getstrs', 'canon'], expected_msg='key: value') + +mark('getstrs') +realm.run([kadminl, 'getstrs', 'alias'], expected_msg='key: value') + +mark('delstr') +realm.run([kadminl, 'delstr', 'alias', 'key']) +realm.run([kadminl, 'getstrs', 'canon'], + expected_msg='(No string attributes.)') + +success('alias tests') diff --git a/src/tests/t_kadmin_acl.py b/src/tests/t_kadmin_acl.py index 31a7fb871..fe762d57c 100755 --- a/src/tests/t_kadmin_acl.py +++ b/src/tests/t_kadmin_acl.py @@ -25,21 +25,33 @@ all_modify = make_client('all_modify') all_rename = make_client('all_rename') all_wildcard = make_client('all_wildcard') all_extract = make_client('all_extract') +all_alias = make_client('all_alias') some_add = make_client('some_add') some_changepw = make_client('some_changepw') some_delete = make_client('some_delete') some_inquire = make_client('some_inquire') some_modify = make_client('some_modify') some_rename = make_client('some_rename') +some_extract = make_client('some_extract') +some_alias = make_client('some_alias') restricted_add = make_client('restricted_add') restricted_modify = make_client('restricted_modify') restricted_rename = make_client('restricted_rename') +restricted_alias = make_client('restricted_alias') wctarget = make_client('wctarget') admin = make_client('user/admin') none = make_client('none') restrictions = make_client('restrictions') onetwothreefour = make_client('one/two/three/four') +realm.run([kadminl, 'alias', 'aliastonone', 'none']) +aliastonone = os.path.join(realm.testdir, 'kadmin_ccache_aliastonone') +realm.kinit('aliastonone', password('none'), + flags=['-S', 'kadmin/admin', '-c', aliastonone]) + +realm.run([kadminl, 'alias', 'aliastounselected', 'unselected']) +realm.run([kadminl, 'alias', 'aliastoselected', 'selected']) + realm.run([kadminl, 'addpol', '-minlife', '1 day', 'minlife']) f = open(os.path.join(realm.testdir, 'acl'), 'w') @@ -53,16 +65,27 @@ all_modify im all_rename ad all_wildcard x all_extract ie +all_alias am some_add a selected +some_add a aliastounselected some_changepw c selected +some_changepw c aliastounselected some_delete d selected +some_delete d aliastounselected some_inquire i selected +some_inquire i aliastounselected some_modify im selected +some_modify im aliastounselected +some_extract ie selected +some_extract ie aliastounselected some_rename d from some_rename a to +some_alias a aliasname +some_alias m canon restricted_add a * +preauth restricted_modify im * +preauth restricted_rename ad * +preauth +restricted_alias ai * +preauth */* d *2/*1 # The next line is a regression test for #8154; it is not used directly. @@ -92,7 +115,13 @@ for pw in (['-pw', 'newpw'], ['-randkey']): expected_msg=msg) kadmin_as(some_changepw, ['cpw'] + args + ['unselected'], expected_code=1, expected_msg=msg) + # Verify that the ACL check is canonicalized. + kadmin_as(some_changepw, ['cpw'] + args + ['aliastounselected'], + expected_code=1, expected_msg=msg) + kadmin_as(some_changepw, ['cpw'] + args + ['aliastoselected']) kadmin_as(none, ['cpw'] + args + ['none']) + kadmin_as(aliastonone, ['cpw'] + args + ['none'], + expected_code=1, expected_msg=msg) realm.run([kadminl, 'modprinc', '-policy', 'minlife', 'none']) msg = "Current password's minimum life has not expired" kadmin_as(none, ['cpw'] + args + ['none'], expected_code=1, @@ -123,6 +152,12 @@ for ks in ([], ['-e', 'aes256-cts']): expected_msg="Operation requires ``add'' privilege") kadmin_as(some_add, ['addprinc'] + args + ['unselected'], expected_code=1, expected_msg="Operation requires ``add'' privilege") + # Verify that the ACL check isn't canonicalized. (We need the alias + # to resolve or we will overwrite it, currently.) + realm.addprinc('unselected') + kadmin_as(some_add, ['addprinc'] + args + ['aliastounselected'], + expected_code=1, expected_msg='already exists') + realm.run([kadminl, 'delprinc', 'unselected']) mark('delprinc') realm.addprinc('unselected', 'pw') @@ -134,6 +169,11 @@ kadmin_as(none, ['delprinc', 'unselected'], expected_code=1, expected_msg="Operation requires ``delete'' privilege") kadmin_as(some_delete, ['delprinc', 'unselected'], expected_code=1, expected_msg="Operation requires ``delete'' privilege") +# Verify that the ACL check isn't canonicalized. +kadmin_as(some_delete, ['delprinc', 'aliastounselected']) +realm.run([kadminl, 'alias', 'aliastounselected', 'unselected']) +kadmin_as(some_delete, ['delprinc', 'aliastoselected'], expected_code=1, + expected_msg="Operation requires ``delete'' privilege") realm.run([kadminl, 'delprinc', 'unselected']) mark('getpol') @@ -155,6 +195,11 @@ kadmin_as(none, ['getprinc', 'selected'], expected_code=1, expected_msg="Operation requires ``get'' privilege") kadmin_as(some_inquire, ['getprinc', 'unselected'], expected_code=1, expected_msg="Operation requires ``get'' privilege") +# Verify that the ACL check is canonicalized. +kadmin_as(some_inquire, ['getprinc', 'aliastounselected'], expected_code=1, + expected_msg="Operation requires ``get'' privilege") +kadmin_as(some_inquire, ['getprinc', 'aliastoselected'], + expected_msg='Principal: selected at KRBTEST.COM') kadmin_as(none, ['getprinc', 'none'], expected_msg='Principal: none at KRBTEST.COM') realm.run([kadminl, 'delprinc', 'selected']) @@ -176,6 +221,11 @@ kadmin_as(none, ['getstrs', 'selected'], expected_code=1, expected_msg="Operation requires ``get'' privilege") kadmin_as(some_inquire, ['getstrs', 'unselected'], expected_code=1, expected_msg="Operation requires ``get'' privilege") +# Verify that the ACL check is canonicalized. +kadmin_as(some_inquire, ['getstrs', 'aliastounselected'], expected_code=1, + expected_msg="Operation requires ``get'' privilege") +kadmin_as(some_inquire, ['getstrs', 'aliastoselected'], + expected_msg='key: value') kadmin_as(none, ['getstrs', 'none'], expected_msg='(No string attributes.)') realm.run([kadminl, 'delprinc', 'selected']) realm.run([kadminl, 'delprinc', 'unselected']) @@ -201,6 +251,10 @@ kadmin_as(all_inquire, ['modprinc', '-maxlife', '1 hour', 'selected'], expected_msg="Operation requires ``modify'' privilege") kadmin_as(some_modify, ['modprinc', '-maxlife', '1 hour', 'unselected'], expected_code=1, expected_msg='Operation requires') +# Verify that the ACL check is canonicalized. +kadmin_as(some_modify, ['modprinc', '-maxlife', '1 hour', 'aliastounselected'], + expected_code=1, expected_msg='Operation requires') +kadmin_as(some_modify, ['modprinc', '-maxlife', '2 hours', 'aliastoselected']) realm.run([kadminl, 'delprinc', 'selected']) realm.run([kadminl, 'delprinc', 'unselected']) @@ -213,6 +267,10 @@ kadmin_as(none, ['purgekeys', 'selected'], expected_code=1, expected_msg="Operation requires ``modify'' privilege") kadmin_as(some_modify, ['purgekeys', 'unselected'], expected_code=1, expected_msg="Operation requires ``modify'' privilege") +# Verify that the ACL check is canonicalized. +kadmin_as(some_modify, ['purgekeys', 'aliastounselected'], expected_code=1, + expected_msg="Operation requires ``modify'' privilege") +kadmin_as(some_modify, ['purgekeys', 'aliastoselected']) kadmin_as(none, ['purgekeys', 'none']) realm.run([kadminl, 'delprinc', 'selected']) realm.run([kadminl, 'delprinc', 'unselected']) @@ -232,6 +290,15 @@ kadmin_as(some_rename, ['renprinc', 'from', 'notto'], expected_code=1, realm.run([kadminl, 'renprinc', 'from', 'notfrom']) kadmin_as(some_rename, ['renprinc', 'notfrom', 'to'], expected_code=1, expected_msg="Insufficient authorization for operation") +# Verify that the ACL check isn't canonicalized. +realm.run([kadminl, 'alias', 'aliastofrom', 'from']) +realm.run([kadminl, 'alias', 'aliastoto', 'to']) +kadmin_as(some_rename, ['renprinc', 'aliastofrom', 'to'], expected_code=1, + expected_msg="Insufficient authorization for operation") +kadmin_as(some_rename, ['renprinc', 'from', 'aliastoto'], expected_code=1, + expected_msg="Insufficient authorization for operation") +realm.run([kadminl, 'delprinc', 'aliastofrom']) +realm.run([kadminl, 'delprinc', 'aliastoto']) kadmin_as(restricted_rename, ['renprinc', 'notfrom', 'to'], expected_code=1, expected_msg="Insufficient authorization for operation") realm.run([kadminl, 'delprinc', 'notfrom']) @@ -245,6 +312,10 @@ kadmin_as(none, ['setstr', 'selected', 'key', 'value'], expected_code=1, expected_msg="Operation requires ``modify'' privilege") kadmin_as(some_modify, ['setstr', 'unselected', 'key', 'value'], expected_code=1, expected_msg='Operation requires') +# Verify that the ACL check is canonicalized. +kadmin_as(some_modify, ['setstr', 'aliastounselected', 'key', 'value'], + expected_code=1, expected_msg='Operation requires') +kadmin_as(some_modify, ['setstr', 'aliastoselected', 'key', 'value']) realm.run([kadminl, 'delprinc', 'selected']) realm.run([kadminl, 'delprinc', 'unselected']) @@ -283,13 +354,24 @@ realm.run([kadminl, 'getprinc', 'type3'], expected_msg='Maximum renewable life: 0 days 02:00:00') mark('extract') +realm.addprinc('selected') +realm.addprinc('unselected') realm.run([kadminl, 'addprinc', '-pw', 'pw', 'extractkeys']) +msg = "Operation requires ``extract-keys'' privilege" kadmin_as(all_wildcard, ['ktadd', '-norandkey', 'extractkeys'], - expected_code=1, - expected_msg="Operation requires ``extract-keys'' privilege") + expected_code=1, expected_msg=msg) kadmin_as(all_extract, ['ktadd', '-norandkey', 'extractkeys']) realm.kinit('extractkeys', flags=['-k']) +kadmin_as(some_extract, ['ktadd', '-norandkey', 'selected']) +kadmin_as(some_extract, ['ktadd', '-norandkey', 'unselected'], expected_code=1, + expected_msg=msg) +# Verify that the ACL check is canonicalized. +kadmin_as(some_extract, ['ktadd', '-norandkey', 'aliastounselected'], + expected_code=1, expected_msg=msg) +kadmin_as(some_extract, ['ktadd', '-norandkey', 'aliastoselected']) os.remove(realm.keytab) +realm.run([kadminl, 'delprinc', 'selected']) +realm.run([kadminl, 'delprinc', 'unselected']) mark('lockdown_keys') kadmin_as(all_modify, ['modprinc', '+lockdown_keys', 'extractkeys']) @@ -312,6 +394,22 @@ kadmin_as(all_extract, ['ktadd', '-norandkey', 'extractkeys']) realm.kinit('extractkeys', flags=['-k']) os.remove(realm.keytab) +mark('alias') +kadmin_as(all_alias, ['alias', 'aliasname', 'canon']) +realm.run([kadminl, 'delprinc', 'aliasname']) +kadmin_as(some_alias, ['alias', 'aliasname', 'canon']) +realm.run([kadminl, 'delprinc', 'aliasname']) +kadmin_as(all_add, ['alias', 'aliasname', 'canon'], expected_code=1, + expected_msg="Insufficient authorization for operation") +kadmin_as(all_inquire, ['alias', 'aliasname', 'canon'], expected_code=1, + expected_msg="Insufficient authorization for operation") +kadmin_as(some_alias, ['alias', 'aliasname', 'notcanon'], expected_code=1, + expected_msg="Insufficient authorization for operation") +kadmin_as(some_alias, ['alias', 'notaliasname', 'canon'], expected_code=1, + expected_msg="Insufficient authorization for operation") +kadmin_as(restricted_alias, ['alias', 'aliasname', 'canon'], expected_code=1, + expected_msg="Insufficient authorization for operation") + # Verify that self-service key changes require an initial ticket. mark('self-service initial ticket') realm.run([kadminl, 'cpw', '-pw', password('none'), 'none']) diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py index c1a717b99..14d57923f 100755 --- a/src/tests/t_kdb.py +++ b/src/tests/t_kdb.py @@ -360,14 +360,14 @@ mark('LDAP service principal aliases') # Test service principal aliases. realm.addprinc('canon', password('canon')) -ldap_modify('dn: krbPrincipalName=canon at KRBTEST.COM,cn=t1,cn=krb5\n' - 'changetype: modify\n' - 'add: krbPrincipalName\n' - 'krbPrincipalName: alias at KRBTEST.COM\n' - 'krbPrincipalName: ent at abc@KRBTEST.COM\n' - '-\n' - 'add: krbCanonicalName\n' - 'krbCanonicalName: canon at KRBTEST.COM\n') +realm.run([kadminl, 'alias', 'alias', 'canon']) +realm.run([kadminl, 'alias', 'ent\\@abc', 'canon']) +out = ldap_search('(krbPrincipalName=canon*)') +if ('krbPrincipalName: canon at KRBTEST.COM' not in out or + 'krbPrincipalName: alias at KRBTEST.COM' not in out or + 'krbPrincipalName: ent at abc@KRBTEST.COM' not in out or + 'krbCanonicalName: canon at KRBTEST.COM' not in out): + fail('expected names not found in canon object') realm.run([kadminl, 'getprinc', 'alias'], expected_msg='Principal: canon at KRBTEST.COM\n') realm.run([kadminl, 'getprinc', 'ent\\@abc'], @@ -382,14 +382,7 @@ realm.kinit(realm.user_princ, password('user'), ['-S', 'alias']) realm.klist(realm.user_princ, 'alias at KRBTEST.COM') # Make sure an alias to the local TGS is still treated like an alias. -ldap_modify('dn: krbPrincipalName=krbtgt/KRBTEST.COM at KRBTEST.COM,' - 'cn=KRBTEST.COM,cn=krb5\n' - 'changetype: modify\n' - 'add:krbPrincipalName\n' - 'krbPrincipalName: tgtalias at KRBTEST.COM\n' - '-\n' - 'add: krbCanonicalName\n' - 'krbCanonicalName: krbtgt/KRBTEST.COM at KRBTEST.COM\n') +realm.run([kadminl, 'alias', 'tgtalias', 'krbtgt/KRBTEST.COM']) realm.run([kadminl, 'getprinc', 'tgtalias'], expected_msg='Principal: krbtgt/KRBTEST.COM at KRBTEST.COM') realm.kinit(realm.user_princ, password('user')) @@ -429,6 +422,23 @@ realm.klist('ent\\@abc at KRBTEST.COM', 'alias at KRBTEST.COM') # Test client name canonicalization in non-krbtgt AS reply realm.kinit('alias', password('canon'), ['-C', '-S', 'kadmin/changepw']) +# Test deleting an alias. +mark('LDAP alias deletion') +realm.run([kadminl, 'delprinc', 'alias']) +realm.run([kadminl, 'getprinc', 'alias'], expected_code=1, + expected_msg='Principal does not exist') +realm.run([kadminl, 'getprinc', 'ent\\@abc'], + expected_msg='Principal: canon at KRBTEST.COM\n') +realm.run([kadminl, 'getprinc', 'canon'], + expected_msg='Principal: canon at KRBTEST.COM\n') + +# Test deleting a canonical name when an alias is present. +realm.run([kadminl, 'delprinc', 'canon']) +realm.run([kadminl, 'getprinc', 'canon'], expected_code=1, + expected_msg='Principal does not exist') +realm.run([kadminl, 'getprinc', 'ent\\@abc'], expected_code=1, + expected_msg='Principal does not exist') + mark('LDAP password history') # Test password history. @@ -551,6 +561,8 @@ realm.run([kdb5_util, 'load', '-update', dumpfile]) out = realm.run([kadminl, 'getprinc', 'pwuser']) if 'Password expiration date: [never]' in out: fail('pw_expiration not preserved across dump and load') +realm.run([kadminl, 'getprinc', 'tgtalias'], + expected_msg='Principal: krbtgt/KRBTEST.COM at KRBTEST.COM') # Destroy the realm. kldaputil(['destroy', '-f']) diff --git a/src/tests/t_tabdump.py b/src/tests/t_tabdump.py index 49531bf49..54117467f 100755 --- a/src/tests/t_tabdump.py +++ b/src/tests/t_tabdump.py @@ -19,7 +19,13 @@ def checkkeys(rows, dumptype, names): realm = K5Realm(start_kdc=False, get_creds=False) +realm.run([kadminl, 'alias', 'useralias', 'user']) +rows = getrows('alias') +checkkeys(rows, 'alias', ["aliasname", "targetname"]) +if (rows[0]['aliasname'] != 'useralias at KRBTEST.COM' or + rows[0]['targetname'] != 'user at KRBTEST.COM'): + fail('tabdump alias principal names') rows = getrows('keyinfo') checkkeys(rows, 'keyinfo', From ghudson at mit.edu Thu Feb 27 16:33:12 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Thu, 27 Feb 2025 16:33:12 -0500 (EST) Subject: krb5 commit: Add database format documentation Message-ID: <20250227213312.DD3C2102C22@krbdev.mit.edu> https://github.com/krb5/krb5/commit/16f8b9821c6306e38c855cbfcab9d0ef309d081f commit 16f8b9821c6306e38c855cbfcab9d0ef309d081f Author: Greg Hudson Date: Mon Jan 20 22:47:54 2025 -0500 Add database format documentation Add a new file under doc/formats documenting the dump format, tl-data formats, alias principals, and the DB2 and LMDB principal and policy formats. Also correct some formatting in cookie.rst, changing a nested list to a code block for consistency with other documentation of binary formats. ticket: 9164 (new) doc/formats/cookie.rst | 38 +++- doc/formats/database_formats.rst | 459 +++++++++++++++++++++++++++++++++++++++ doc/formats/index.rst | 1 + 3 files changed, 486 insertions(+), 12 deletions(-) diff --git a/doc/formats/cookie.rst b/doc/formats/cookie.rst index e32365daa..3c7d0b03c 100644 --- a/doc/formats/cookie.rst +++ b/doc/formats/cookie.rst @@ -1,3 +1,5 @@ +.. highlight:: abnf + KDC cookie format ================= @@ -42,7 +44,9 @@ principal name with realm, marshalled according to :rfc:`1964` section 2.1.1. The plain text of the encrypted part of a cookie is the DER encoding -of the following ASN.1 type:: +of the following ASN.1 type: + +.. code-block:: bnf SecureCookie ::= SEQUENCE { time INTEGER, @@ -63,17 +67,27 @@ SPAKE cookie format (version 1) ------------------------------- Inside the SecureCookie wrapper, a data value of type 151 contains -state for SPAKE pre-authentication. This data is the concatenation of -the following: - -* a two-byte big-endian version number with the value 1 -* a two-byte big-endian stage number -* a four-byte big-endian group number -* a four-byte big-endian length and data for the SPAKE value -* a four-byte big-endian length and data for the transcript hash -* zero or more second factor records, each consisting of: - - a four-byte big-endian second-factor type - - a four-byte big-endian length and data +state for SPAKE pre-authentication. This data has the following +binary format with big-endian integer encoding: + +.. code-block:: bnf + + cookie ::= + version (16 bits) [with the value 1] + stage number (16 bits) + group number (32 bits) + SPAKE value length (32 bits) + SPAKE value + transcript hash length (32 bits) + transcript hash + second factor record 1 (factor-record) + second factor record 2 (factor-record) + ... + + factor-record ::= + second factor type (32 bits) + second factor data length (32 bits) + second factor data The stage value is 0 if the cookie was sent with a challenge message. Otherwise it is 1 for the first encdata message sent by the KDC during diff --git a/doc/formats/database_formats.rst b/doc/formats/database_formats.rst new file mode 100644 index 000000000..fca5979c1 --- /dev/null +++ b/doc/formats/database_formats.rst @@ -0,0 +1,459 @@ +Kerberos Database (KDB) Formats +=============================== + +Dump format +----------- + +Files created with the :ref:`kdb5_util(8)` **dump** command begin with +a versioned header "kdb5_util load_dump version 7". This version has +been in use since MIT krb5 release 1.11; some previous versions are +supported but are not described here. + +Each subsequent line of the dump file contains one or more +tab-separated fields describing either a principal entry or a policy +entry. The fields of a principal entry line are: + +* the word "princ" +* the string "38" (this was originally a length field) +* the length of the principal name in string form +* the decimal number of tag-length data elements +* the decimal number of key-data elements +* the string "0" (this was originally an extension length field) +* the principal name in string form +* the principal attributes as a decimal number; when converted to + binary, the bits from least significant to most significant are: + + - disallow_postdated + - disallow_forwardable + - disallow_tgt_based + - disallow_renewable + - disallow_proxiable + - disallow_dup_skey + - disallow_all_tix + - requires_preauth + - requires_hwauth + - requires_pwchange + - disallow_svr + - pwchange_service + - support_desmd5 + - new_princ + - ok_as_delegate + - ok_to_auth_as_delegate + - no_auth_data_required + - lockdown_keys + +* the maximum ticket lifetime, as a decimal number of seconds +* the maximum renewable ticket lifetime, as a decimal number of seconds +* the principal expiration time, as a decimal POSIX timestamp +* the password expiration time, as a decimal POSIX timestamp +* the last successful authentication time, as a decimal POSIX + timestamp +* the last failed authentication time, as a decimal POSIX timestamp +* the decimal number of failed authentications since the last + successful authentication time +* for each tag-length data value: + + - the tag value in decimal + - the length in decimal + - the data as a lowercase hexadecimal byte string, or "-1" if the length is 0 + +* for each key-data element: + + - the string "2" if this element has non-normal salt type, "1" + otherwise + - the key version number of this element + - the encryption type + - the length of the encrypted key value + - the encrypted key as a lowercase hexadecimal byte string + - if this element has non-normal salt type: + + - the salt type + - the length of the salt data + - the salt data as a lowercase hexadecimal byte string, or the + string "-1" if the salt data length is 0 + +* the string "-1;" (this was originally an extension field) + +The fields of a policy entry line are: + +* the string "policy" +* the policy name +* the minimum password lifetime as a decimal number of seconds +* the maximum password lifetime as a decimal number of seconds +* the minimum password length, in decimal +* the minimum number of character classes, in decimal +* the number of historical keys to be stored, in decimal +* the policy reference count (no longer used) +* the maximum number of failed authentications before lockout +* the time interval after which the failed authentication count is + reset, as a decimal number of seconds +* the lockout duration, as a decimal number of seconds +* the required principal attributes, in decimal (currently unenforced) +* the maximum ticket lifetime as a decimal number of seconds + (currently unenforced) +* the maximum renewable lifetime as a decimal number of seconds + (currently unenforced) +* the allowed key/salt types, or "-" if unrestricted +* the number of tag-length values +* for each tag-length data value: + + - the tag value in decimal + - the length in decimal + - the data as a lowercase hexadecimal byte string, or "-1" if the + length is 0 + + +Tag-length data formats +----------------------- + +The currently defined tag-length data types are: + +* (1) last password change: a four-byte little-endian POSIX timestamp + giving the last password change time +* (2) last modification data: a four-byte little-endian POSIX + timestamp followed by a zero-terminated principal name in string + form, giving the time of the last principal change and the principal + who performed it +* (3) kadmin data: the XDR encoding of a per-principal kadmin data + record (see below) +* (8) master key version: a two-byte little-endian integer containing + the master key version used to encrypt this principal's key data +* (9) active kvno: see below +* (10) master key auxiliary data: see below +* (11) string attributes: one or more iterations of a zero-terminated + string key followed by a zero-terminated string value +* (12) alias target principal: a zero-terminated principal name in + string form +* (255) LDAP object information: see below +* (768) referral padata: a DER-encoded PA-SVR-REFERRAL-DATA to be sent + to a TGS-REQ client within encrypted padata (see Appendix A of + :rfc:`1606`) +* (1792) last admin unlock: a four-byte little-endian POSIX timestamp + giving the time of the last administrative account unlock +* (32767) database arguments: a zero-terminated key=value string (may + appear multiple times); used by the kadmin protocol to + communicate -x arguments to kadmind + +Per-principal kadmin data +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Per-principal kadmin data records use a modified XDR encoding of the +kadmin_data type defined as follows: + +.. code-block:: c + + struct key_data { + int numfields; + unsigned int kvno; + int enctype; + int salttype; + unsigned int keylen; + unsigned int saltlen; + opaque key<>; + opaque salt<>; + }; + + struct hist_entry { + key_data keys<>; + }; + + struct kadmin_data { + int version_number; + nullstring policy; + int aux_attributes; + unsigned int old_key_next; + unsigned int admin_history_kvno; + hist_entry old_keysets<>; + }; + +The type "nullstring" uses a custom string encoder where the length +field is zero or the string length plus one; a length of zero +indicates that no policy object is specified for the principal. The +field "version_number" contains 0x12345C01. The aux_attributes field +contains the bit 0x800 if a policy object is associated with the +principal. + +Within a key_data record, numfields is 2 if the key data has +non-normal salt type, 1 otherwise. + +Active kvno and master key auxiliary data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These types only appear in the entry of the master key principal +(K/M). They use little-endian binary integer encoding. + +The active kvno table determines which master key version is active +for a given timestamp. It uses the following binary format: + +.. code-block:: bnf + + active-key-version-table ::= + version (16 bits) [with the value 1] + version entry 1 (key-version-entry) + version entry 2 (key-version-entry) + ... + + key-version-entry ::= + key version (16 bits) + timestamp (32 bits) [when this key version becomes active] + +The master key auxiliary data record contains copies of the current +master key encrypted in each older master key. It uses the following +binary format: + +.. code-block:: bnf + + master-key-aux ::= + version (16 bits) [with the value 1] + key entry 1 (key-entry) + key entry 2 (key-entry) + ... + + key-entry ::= + old master key version (16 bits) + latest master key version (16 bits) + latest master key encryption type (16 bits) + encrypted key length (16 bits) + encrypted key contents + +LDAP object information +~~~~~~~~~~~~~~~~~~~~~~~ + +This type appears in principal entries retrieved with the LDAP KDB +module. The value uses the following binary format, using big-endian +integer encoding: + +.. code-block:: bnf + + ldap-principal-data ::= + record 1 (ldap-tl-data) + record 2 (ldap-tl-data) + ... + + ldap-tl-data ::= + type (8 bits) + length (16 bits) + data + +The currently defined ldap-tl-data types are (all integers are +big-endian): + +* (1) principal type: 16 bits containing the value 1, indicating that + the LDAP object containing the principal entry is a standalone + principal object +* (2) principal count: 16 bits containing the number of + krbPrincipalName values in the LDAP object +* (3) user DN: the string representation of the distinguished name of + the LDAP object +* (5) attribute mask: 16 bits indicating which Kerberos-specific LDAP + attributes are present in the LDAP object (see below) +* (7) link DN: the string representation of the distinguished name of + an LDAP object this object is linked to; may appear multiple times + +When converted to binary, the attribute mask bits, from least +significant to most significant, correspond to the following LDAP +attributes: + +* krbMaxTicketLife +* krbMaxRenewableAge +* krbTicketFlags +* krbPrincipalExpiration +* krbTicketPolicyReference +* krbPrincipalAuthInd +* krbPwdPolicyReference +* krbPasswordExpiration +* krbPrincipalKey +* krbLastPwdChange +* krbExtraData +* krbLastSuccessfulAuth +* krbLastFailedAuth +* krbLoginFailedCount +* krbLastAdminUnlock +* krbPwdHistory + + +Alias principal entries +----------------------- + +To allow aliases to be represented in dump files and within the +incremental update protocol, the krb5 database library supports the +concept of an alias principal entry. An alias principal entry +contains an alias target principal in its tag-length data, has its +attributes set to disallow_all_tix, and has zero or empty values for +all other fields. The database glue library recognizes alias entries +and iteratively looks up the alias target up to a depth of 10 chained +aliases. (Added in release 1.22.) + + +DB2 principal and policy formats +-------------------------------- + +The DB2 KDB module uses the string form of a principal name, with zero +terminator, as a lookup key for principal entries. Principal entry +values use the following binary format with little-endian integer +encoding: + +.. code-block:: bnf + + db2-principal-entry ::= + len (16 bits) [always has the value 38] + attributes (32 bits) + max ticket lifetime (32 bits) + max renewable lifetime (32 bits) + principal expiration timestamp (32 bits) + password expiration timestamp (32 bits) + last successful authentication timestamp (32 bits) + last failed authentication timestamp (32 bits) + failed authentication counter (32 bits) + number of tag-length elements (16 bits) + number of key-data elements (16 bits) + length of string-form principal with zero terminator (16 bits) + string-form principal with zero terminator + tag-length entry 1 (tag-length-data) + tag-length entry 2 (tag-length-data) + ... + key-data entry 1 (key-data) + key-data entry 2 (key-data) + ... + + tag-length-data ::= + type tag (16 bits) + data length (16 bits) + data + + key-data ::= + salt indicator (16 bits) [1 for default salt, 2 otherwise] + key version (16 bits) + encryption type (16 bits) + encrypted key length (16 bits) + encrypted key + salt type (16 bits) [omitted if salt indicator is 1] + salt data length (16 bits) [omitted if salt indicator is 1] + salt data [omitted if salt indicator is 1] + +DB2 policy entries reside in a separate database file. The lookup key +is the policy name with zero terminator. Policy entry values use a +modified XDR encoding of the policy type defined as follows: + +.. code-block:: c + + struct tl_data { + int type; + opaque data<>; + tl_data *next; + }; + + struct policy { + int version_number; + unsigned int min_life; + unsigned int max_pw_life; + unsigned int min_length; + unsigned int min_classes; + unsigned int history_num; + unsigned int refcount; + unsigned int max_fail; + unsigned int failcount_interval; + unsigned int lockout_duration; + unsigned int attributes; + unsigned int max_ticket_life; + unsigned int max_renewable_life; + nullstring allowed_keysalts; + int n_tl_data; + tl_data *tag_length_data; + }; + +The type "nullstring" uses the same custom encoder as in the +per-principal kadmin data. + +The field "version_number" contains 0x12345D01, 0x12345D02, or +0x12345D03 for versions 1, 2, and 3 respectively. Versions 1 and 2 +omit the fields "attributes" through "tag_length_data". Version 1 +also omits the fields "max_fail" through "lockout_duration". Encoding +uses the lowest version that can represent the policy entry. + +The field "refcount" is no longer used and its value is ignored. + + +LMDB principal and policy formats +--------------------------------- + +In the LMDB KDB module, principal entries are stored in the +"principal" database within the main LMDB environment (typically named +"principal.mdb"), with the exception of lockout-related fields which +are stored in the "lockout" table of the lockout LMDB environment +(typically named "principal.lockout.mdb"). For both databases the key +is the principal name in string form, with no zero terminator. Values +in the "principal" database use the following binary format with +little-endian integer encoding: + +.. code-block:: bnf + + lmdb-principal-entry ::= + attributes (32 bits) + max ticket lifetime (32 bits) + max renewable lifetime (32 bits) + principal expiration timestamp (32 bits) + password expiration timestamp (32 bits) + number of tag-length elements (16 bits) + number of key-data elements (16 bits) + tag-length entry 1 (tag-length-data) + tag-length entry 2 (tag-length-data) + ... + key-data entry 1 (key-data) + key-data entry 2 (key-data) + ... + + tag-length-data ::= + type tag (16 bits) + data length (16 bits) + data value + + key-data ::= + salt indicator (16 bits) [1 for default salt, 2 otherwise] + key version (16 bits) + encryption type (16 bits) + encrypted key length (16 bits) + encrypted key + salt type (16 bits) [omitted if salt indicator is 1] + salt data length (16 bits) [omitted if salt indicator is 1] + salt data [omitted if salt indicator is 1] + +Values in the "lockout" database have the following binary format with +little-endian integer encoding: + +.. code-block:: bnf + + lmdb-lockout-entry ::= + last successful authentication timestamp (32 bits) + last failed authentication timestamp (32 bits) + failed authentication counter (32 bits) + +In the "policy" database, the lookup key is the policy name with no +zero terminator. Values in this database use the following binary +format with little-endian integer encoding: + +.. code-block:: bnf + + lmdb-policy-entry ::= + minimum password lifetime (32 bits) + maximum password lifetime (32 bits) + minimum password length (32 bits) + minimum character classes (32 bits) + number of historical keys (32 bits) + maximum failed authentications before lockout (32 bits) + time interval to reset failed authentication counter (32 bits) + lockout duration (32 bits) + required principal attributes (32 bits) [currently unenforced] + maximum ticket lifetime (32 bits) [currently unenforced] + maximum renewable lifetime (32 bits) [currently unenforced] + allowed key/salt type specification length [32 bits] + allowed key/salt type specification + number of tag-length values (16 bits) + tag-length entry 1 (tag-length-data) + tag-length entry 2 (tag-length-data) + ... + + tag-length-data ::= + type tag (16 bits) + data length (16 bits) + data value diff --git a/doc/formats/index.rst b/doc/formats/index.rst index 47dea12fc..819b839de 100644 --- a/doc/formats/index.rst +++ b/doc/formats/index.rst @@ -9,3 +9,4 @@ Protocols and file formats rcache_file_format cookie freshness_token + database_formats From ghudson at mit.edu Thu Feb 27 17:28:50 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Thu, 27 Feb 2025 17:28:50 -0500 (EST) Subject: krb5 commit: Build PKINIT on Windows Message-ID: <20250227222850.607E8102C01@krbdev.mit.edu> https://github.com/krb5/krb5/commit/1bea471f6ec445a80d966de4233b270c4c2801c4 commit 1bea471f6ec445a80d966de4233b270c4c2801c4 Author: Ken Hornstein Date: Fri Dec 6 15:29:33 2024 -0500 Build PKINIT on Windows Include PKINIT in the Windows build if OPENSSL_DIR is set. In the installer, require PKINIT to be built, and require OPENSSL_VERSION to be set in the environment. Include the PKINIT module and a copy of the OpenSSL libcrypto DLL. Document the OpenSSL dependency in windows/README. Also clarify the Visual Studio dependencies, and restore the prep-windows build step which was accidentally removed by commit 5f33fb2cf19562175c8271cb9c54a67212f63b93. [ghudson at mit.edu: added CI and installer integration; edited README and commit message] ticket: 9162 (new) .github/workflows/build.yml | 2 + src/Makefile.in | 22 +++- src/config/win-pre.in | 12 ++- src/include/win-mac.h | 4 + src/lib/krb5_32.def | 7 ++ src/plugins/preauth/pkinit/Makefile.in | 23 ++++ src/plugins/preauth/pkinit/pkinit.def | 3 + src/plugins/preauth/pkinit/pkinit.h | 2 + src/plugins/preauth/pkinit/pkinit_accessor.c | 4 +- src/plugins/preauth/pkinit/pkinit_accessor.h | 2 +- src/plugins/preauth/pkinit/pkinit_clnt.c | 1 - src/plugins/preauth/pkinit/pkinit_crypto_openssl.c | 116 ++++++++++----------- src/plugins/preauth/pkinit/pkinit_identity.c | 1 - src/plugins/preauth/pkinit/pkinit_matching.c | 3 +- src/windows/README | 64 +++++++----- src/windows/installer/wix/config.wxi | 2 + src/windows/installer/wix/features.wxi | 2 + src/windows/installer/wix/files.wxi | 7 ++ src/windows/installer/wix/platform.wxi | 4 + 19 files changed, 186 insertions(+), 95 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 183b7c293..c16e77c43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,6 +70,8 @@ jobs: runs-on: windows-latest env: KRB_INSTALL_DIR: C:\kfw + OPENSSL_DIR: C:\Program Files\OpenSSL + OPENSSL_VERSION: 1_1 steps: - name: Checkout repository uses: actions/checkout at v1 diff --git a/src/Makefile.in b/src/Makefile.in index e2286c5ee..01fb060f7 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -32,7 +32,8 @@ SUBDIRS=util include lib \ plugins/tls/k5tls \ kdc kadmin kprop clients appl tests \ config-files build-tools man doc @po@ -WINSUBDIRS=include util lib ccapi windows clients appl plugins\preauth\spake +WINSUBDIRS=include util lib ccapi windows clients appl plugins\preauth\spake \ + $(PKINIT_SUBDIR) BUILDTOP=$(REL). SRCS = @@ -105,6 +106,17 @@ config-windows: Makefile-windows # $(MAKE) -$(MFLAGS) # cd .. +# +# Build the pkinit plugin if OpenSSL was configured +# +##DOS##!ifdef OPENSSL_DIR +##DOS##PKINIT_SUBDIR=plugins\preauth\pkinit +##DOS##PKINIT_MAKEFILE=$(PKINIT_SUBDIR)\Makefile +##DOS##!else +##DOS##PKINIT_SUBDIR= +##DOS##PKINIT_MAKEFILE= +##DOS##!endif + # # We need outpre-dir explicitly in here because we may # try to build wconfig on a config-windows. @@ -154,7 +166,7 @@ WINMAKEFILES=Makefile \ windows\Makefile windows\lib\Makefile windows\ms2mit\Makefile \ windows\kfwlogon\Makefile windows\leashdll\Makefile \ windows\leash\Makefile windows\leash\htmlhelp\Makefile \ - plugins\preauth\spake\Makefile + plugins\preauth\spake\Makefile $(PKINIT_MAKEFILE) ##DOS##Makefile-windows: $(MKFDEP) $(WINMAKEFILES) @@ -278,6 +290,8 @@ WINMAKEFILES=Makefile \ ##DOS## $(WCONFIG) config < $@.in > $@ ##DOS##plugins\preauth\spake\Makefile: plugins\preauth\spake\Makefile.in $(MKFDEP) ##DOS## $(WCONFIG) config < $@.in > $@ +##DOS##plugins\preauth\pkinit\Makefile: plugins\preauth\pkinit\Makefile.in $(MKFDEP) +##DOS## $(WCONFIG) config < $@.in > $@ clean-windows:: Makefile-windows @@ -485,6 +499,10 @@ install-windows: $(INSTALLDBGSYMS) clients\kswitch\$(OUTPRE)kswitch.pdb "$(KRB_INSTALL_DIR)\bin\." copy plugins\preauth\spake\$(OUTPRE)$(SPAKELIB).dll "$(KRB_INSTALL_DIR)\bin\plugins\preauth\." $(INSTALLDBGSYMS) plugins\preauth\spake\$(OUTPRE)$(SPAKELIB).pdb "$(KRB_INSTALL_DIR)\bin\plugins\preauth\." +##DOS##!ifdef OPENSSL_DIR + copy plugins\preauth\pkinit\$(OUTPRE)$(PKINITLIB).dll "$(KRB_INSTALL_DIR)\bin\plugins\preauth\." + $(INSTALLDBGSYMS) plugins\preauth\pkinit\$(OUTPRE)$(PKINITLIB).pdb "$(KRB_INSTALL_DIR)\bin\plugins\preauth\." +##DOS##!endif check-prerecurse: runenv.py $(RM) $(SKIPTESTS) diff --git a/src/config/win-pre.in b/src/config/win-pre.in index 753fde7cc..0bcb280df 100644 --- a/src/config/win-pre.in +++ b/src/config/win-pre.in @@ -117,7 +117,7 @@ KFWFLAGS=-DUSE_LEASH=1 -DDEBUG -D_CRTDBG_MAP_ALLOC CC=cl PDB_OPTS=-Fd$(OUTPRE)\ -FD -CPPFLAGS=-I$(top_srcdir)\include -I$(top_srcdir)\include\krb5 $(DNSFLAGS) -DWIN32_LEAN_AND_MEAN -DKRB5_DEPRECATED=1 -DKRB5_PRIVATE -D_CRT_SECURE_NO_DEPRECATE $(KFWFLAGS) $(TIME_T_FLAGS) +CPPFLAGS=-I$(top_srcdir)\include -I$(top_srcdir)\include\krb5 $(DNSFLAGS) -DWIN32_LEAN_AND_MEAN -DKRB5_DEPRECATED=1 -DKRB5_PRIVATE -D_CRT_SECURE_NO_DEPRECATE $(KFWFLAGS) $(TIME_T_FLAGS) $(OSSLINCLUDE) # Treat the following warnings as errors: # 4020: too many actual parameters # 4024: different types for formal and actual parameter @@ -189,6 +189,16 @@ GLIB=$(BUILDTOP)\lib\$(OUTPRE)gssapi$(BITS).lib CCLIB=krbcc$(BITS) SPAKELIB=spake$(BITS) +!ifdef OPENSSL_DIR +OSSLLIB="$(OPENSSL_DIR)\lib\libcrypto.lib" +OSSLINC="-I$(OPENSSL_DIR)\include" +PKINITLIB=pkinit$(BITS) +!else +OSSLLIB= +OSSLINC= +PKINITLIB= +!endif + KRB4_INCLUDES=-I$(BUILDTOP)/include/kerberosIV COM_ERR_DEPS = $(BUILDTOP)/include/com_err.h diff --git a/src/include/win-mac.h b/src/include/win-mac.h index 0fd3a29e5..b2a460131 100644 --- a/src/include/win-mac.h +++ b/src/include/win-mac.h @@ -236,4 +236,8 @@ HINSTANCE get_lib_instance(void); #define KRB5_CALLCONV_C #endif +#ifndef PKCS11_MODNAME +#define PKCS11_MODNAME "C:\\Program Files\\OpenSC Project\\OpenSC\\pkcs11\\opensc-pkcs11.dll" +#endif + #endif /* _KRB5_WIN_MAC_H */ diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def index b1610974b..c5a460185 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -510,3 +510,10 @@ EXPORTS k5_sname_compare @474 ; PRIVATE GSSAPI krb5_kdc_sign_ticket @475 ; krb5_kdc_verify_ticket @476 ; + +; new in 1.22 +; private symbols used by PKINIT module + encode_krb5_sp80056a_other_info @477 ; PRIVATE + encode_krb5_pkinit_supp_pub_info @478 ; PRIVATE + krb5int_copy_data_contents @479 ; PRIVATE + krb5_free_pa_data @480 ; PRIVATE diff --git a/src/plugins/preauth/pkinit/Makefile.in b/src/plugins/preauth/pkinit/Makefile.in index 86f143d72..d57088c4d 100644 --- a/src/plugins/preauth/pkinit/Makefile.in +++ b/src/plugins/preauth/pkinit/Makefile.in @@ -12,6 +12,9 @@ SHLIB_EXPDEPS = \ $(TOPLIBD)/libkrb5$(SHLIBEXT) SHLIB_EXPLIBS= -lkrb5 $(COM_ERR_LIB) -lk5crypto -lcrypto $(DL_LIB) $(SUPPORT_LIB) $(LIBS) +WINLIBS = $(KLIB) $(SLIB) $(PLIB) $(CLIB) $(OSSLLIB) +OSSLINCLUDE = $(OSSLINC) + STLIBOBJS= \ pkinit_accessor.o \ pkinit_srv.o \ @@ -35,6 +38,19 @@ SRCS= \ $(srcdir)/pkinit_matching.c \ $(srcdir)/pkinit_crypto_openssl.c +# +# Don't include pkinit_srv.c in the Windows object list since we +# don't need it. +# +OBJS= $(OUTPRE)pkinit_accessor.$(OBJEXT) \ + $(OUTPRE)pkinit_lib.$(OBJEXT) \ + $(OUTPRE)pkinit_clnt.$(OBJEXT) \ + $(OUTPRE)pkinit_constants.$(OBJEXT) \ + $(OUTPRE)pkinit_profile.$(OBJEXT) \ + $(OUTPRE)pkinit_identity.$(OBJEXT) \ + $(OUTPRE)pkinit_matching.$(OBJEXT) \ + $(OUTPRE)pkinit_crypto_openssl.$(OBJEXT) + all-unix: all-liblinks install-unix: install-libs clean-unix:: clean-liblinks clean-libs clean-libobjs @@ -48,6 +64,13 @@ check-unix: pkinit_kdf_test pkinit_kdf_test: pkinit_kdf_test.o $(STLIBOBJS) $(SHLIB_EXPDEPS) $(CC_LINK) -o $@ pkinit_kdf_test.o $(STLIBOBJS) $(SHLIB_EXPLIBS) +all-windows: $(OUTPRE)$(PKINITLIB).dll +clean-windows:: + $(RM) $(OUTPRE)$(PKINITLIB).dll + +$(OUTPRE)$(PKINITLIB).dll: pkinit.def $(OBJS) + link /dll $(LOPTS) -def:pkinit.def -out:$*.dll $(OBJS) $(WINLIBS) + @libnover_frag@ @libobj_frag@ diff --git a/src/plugins/preauth/pkinit/pkinit.def b/src/plugins/preauth/pkinit/pkinit.def new file mode 100644 index 000000000..4736e35e8 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit.def @@ -0,0 +1,3 @@ +EXPORTS + + clpreauth_pkinit_initvt diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h index 7ba7155bb..200e75afe 100644 --- a/src/plugins/preauth/pkinit/pkinit.h +++ b/src/plugins/preauth/pkinit/pkinit.h @@ -97,7 +97,9 @@ static inline void pkiDebug (const char *fmt, ...) { } /* Solaris compiler doesn't grok __FUNCTION__ * hack for now. Fix all the uses eventually. */ +#ifndef _WIN32 #define __FUNCTION__ __func__ +#endif /* Macros to deal with converting between various data types... */ #define PADATA_TO_KRB5DATA(pad, k5d) \ diff --git a/src/plugins/preauth/pkinit/pkinit_accessor.c b/src/plugins/preauth/pkinit/pkinit_accessor.c index 0908f1b9b..5d9463e84 100644 --- a/src/plugins/preauth/pkinit/pkinit_accessor.c +++ b/src/plugins/preauth/pkinit/pkinit_accessor.c @@ -69,8 +69,8 @@ krb5_error_code krb5_error_code (*k5int_encode_krb5_kdc_req_body)(const krb5_kdc_req *rep, krb5_data **code); -void KRB5_CALLCONV -(*k5int_krb5_free_kdc_req)(krb5_context, krb5_kdc_req * ); +void +(KRB5_CALLCONV *k5int_krb5_free_kdc_req)(krb5_context, krb5_kdc_req * ); void (*k5int_set_prompt_types)(krb5_context, krb5_prompt_type *); diff --git a/src/plugins/preauth/pkinit/pkinit_accessor.h b/src/plugins/preauth/pkinit/pkinit_accessor.h index e510ab624..37e2b5307 100644 --- a/src/plugins/preauth/pkinit/pkinit_accessor.h +++ b/src/plugins/preauth/pkinit/pkinit_accessor.h @@ -66,7 +66,7 @@ extern krb5_error_code (*k5int_decode_krb5_td_trusted_certifiers) extern krb5_error_code (*k5int_encode_krb5_kdc_req_body) (const krb5_kdc_req *rep, krb5_data **code); -extern void KRB5_CALLCONV (*k5int_krb5_free_kdc_req) +extern void (KRB5_CALLCONV *k5int_krb5_free_kdc_req) (krb5_context, krb5_kdc_req * ); extern void (*k5int_set_prompt_types) (krb5_context, krb5_prompt_type *); diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c index 305a59da3..9312ff4b1 100644 --- a/src/plugins/preauth/pkinit/pkinit_clnt.c +++ b/src/plugins/preauth/pkinit/pkinit_clnt.c @@ -33,7 +33,6 @@ #include "pkinit.h" #include "k5-json.h" -#include #include /** diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c index 14370ae34..8098028eb 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c @@ -34,7 +34,6 @@ #include "k5-err.h" #include "k5-hex.h" #include "pkinit.h" -#include #include #include @@ -1867,7 +1866,7 @@ cms_signeddata_create(krb5_context context, #ifdef DEBUG_SIG print_buffer(sig, sig_len); #endif - free(abuf); + OPENSSL_free(abuf); if (retval) goto cleanup2; @@ -4304,12 +4303,9 @@ pkinit_get_certs_dir(krb5_context context, krb5_principal princ) { krb5_error_code retval = ENOMEM; - DIR *d = NULL; - struct dirent *dentry = NULL; - char certname[1024]; - char keyname[1024]; - int i = 0, len; - char *dirname, *suf; + int ncreds = 0, len, i; + char *dirname, *suf, *name, **fnames = NULL; + char *certname = NULL, *keyname = NULL; if (idopts->cert_filename == NULL) { TRACE_PKINIT_NO_CERT(context); @@ -4317,53 +4313,51 @@ pkinit_get_certs_dir(krb5_context context, } dirname = idopts->cert_filename; - d = opendir(dirname); - if (d == NULL) - return errno; + retval = k5_dir_filenames(dirname, &fnames); + if (retval) + return retval; /* * We'll assume that certs are named XXX.crt and the corresponding * key is named XXX.key */ - while ((i < MAX_CREDS_ALLOWED) && (dentry = readdir(d)) != NULL) { - /* Ignore subdirectories and anything starting with a dot */ -#ifdef DT_DIR - if (dentry->d_type == DT_DIR) - continue; -#endif - if (dentry->d_name[0] == '.') + for (i = 0; fnames[i] != NULL; i++) { + /* Ignore anything starting with a dot */ + name = fnames[i]; + if (name[0] == '.') continue; - len = strlen(dentry->d_name); + len = strlen(name); if (len < 5) continue; - suf = dentry->d_name + (len - 4); + suf = name + (len - 4); if (strncmp(suf, ".crt", 4) != 0) continue; - /* Checked length */ - if (strlen(dirname) + strlen(dentry->d_name) + 2 > sizeof(certname)) { - pkiDebug("%s: Path too long -- directory '%s' and file '%s'\n", - __FUNCTION__, dirname, dentry->d_name); - continue; - } - snprintf(certname, sizeof(certname), "%s/%s", dirname, dentry->d_name); - snprintf(keyname, sizeof(keyname), "%s/%s", dirname, dentry->d_name); + retval = k5_path_join(dirname, name, &certname); + if (retval) + goto cleanup; + retval = k5_path_join(dirname, name, &keyname); + if (retval) + goto cleanup; + len = strlen(keyname); keyname[len - 3] = 'k'; keyname[len - 2] = 'e'; keyname[len - 1] = 'y'; retval = pkinit_load_fs_cert_and_key(context, id_cryptoctx, - certname, keyname, i); - if (retval == 0) { - TRACE_PKINIT_LOADED_CERT(context, dentry->d_name); - i++; + certname, keyname, ncreds); + free(certname); + free(keyname); + certname = keyname = NULL; + if (!retval) { + TRACE_PKINIT_LOADED_CERT(context, name); + if (++ncreds >= MAX_CREDS_ALLOWED) + break; } - else - continue; } - if (!id_cryptoctx->defer_id_prompt && i == 0) { + if (!id_cryptoctx->defer_id_prompt && ncreds == 0) { TRACE_PKINIT_NO_CERT_AND_KEY(context, idopts->cert_filename); retval = ENOENT; goto cleanup; @@ -4372,9 +4366,9 @@ pkinit_get_certs_dir(krb5_context context, retval = 0; cleanup: - if (d) - closedir(d); - + k5_free_filenames(fnames); + free(certname); + free(keyname); return retval; } @@ -5166,34 +5160,28 @@ load_cas_and_crls_dir(krb5_context context, char *dirname) { krb5_error_code retval = EINVAL; - DIR *d = NULL; - struct dirent *dentry = NULL; - char filename[1024]; + char **fnames = NULL, *filename; + int i; if (dirname == NULL) return EINVAL; - d = opendir(dirname); - if (d == NULL) - return ENOENT; + retval = k5_dir_filenames(dirname, &fnames); + if (retval) + return retval; - while ((dentry = readdir(d))) { - if (strlen(dirname) + strlen(dentry->d_name) + 2 > sizeof(filename)) { - pkiDebug("%s: Path too long -- directory '%s' and file '%s'\n", - __FUNCTION__, dirname, dentry->d_name); - goto cleanup; - } - /* Ignore subdirectories and anything starting with a dot */ -#ifdef DT_DIR - if (dentry->d_type == DT_DIR) - continue; -#endif - if (dentry->d_name[0] == '.') + for (i = 0; fnames[i] != NULL; i++) { + /* Ignore anything starting with a dot */ + if (fnames[i][0] == '.') continue; - snprintf(filename, sizeof(filename), "%s/%s", dirname, dentry->d_name); + + retval = k5_path_join(dirname, fnames[i], &filename); + if (retval) + goto cleanup; retval = load_cas_and_crls(context, plg_cryptoctx, req_cryptoctx, id_cryptoctx, catype, filename); + free(filename); if (retval) goto cleanup; } @@ -5201,9 +5189,7 @@ load_cas_and_crls_dir(krb5_context context, retval = 0; cleanup: - if (d != NULL) - closedir(d); - + k5_free_filenames(fnames); return retval; } @@ -5712,3 +5698,13 @@ parse_dh_min_bits(krb5_context context, const char *str) TRACE_PKINIT_DH_INVALID_MIN_BITS(context, str); return PKINIT_DEFAULT_DH_MIN_BITS; } + +#ifdef _WIN32 +BOOL WINAPI +DllMain(HANDLE hModule, DWORD fdwReason, LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + pkinit_openssl_init__auxinit(); + return TRUE; +} +#endif /* _WIN32 */ diff --git a/src/plugins/preauth/pkinit/pkinit_identity.c b/src/plugins/preauth/pkinit/pkinit_identity.c index 2e29a8c45..0dcfcfc46 100644 --- a/src/plugins/preauth/pkinit/pkinit_identity.c +++ b/src/plugins/preauth/pkinit/pkinit_identity.c @@ -30,7 +30,6 @@ */ #include "pkinit.h" -#include static void free_list(char **list) diff --git a/src/plugins/preauth/pkinit/pkinit_matching.c b/src/plugins/preauth/pkinit/pkinit_matching.c index b42485a50..0ea072c88 100644 --- a/src/plugins/preauth/pkinit/pkinit_matching.c +++ b/src/plugins/preauth/pkinit/pkinit_matching.c @@ -33,9 +33,8 @@ #include #include #include -#include -#include #include "pkinit.h" +#include "k5-regex.h" typedef struct _pkinit_cert_info pkinit_cert_info; diff --git a/src/windows/README b/src/windows/README index 5ceff9adc..7ca84a8ee 100644 --- a/src/windows/README +++ b/src/windows/README @@ -10,13 +10,24 @@ To build Kerberos 5 on Windows, you will need the following: * A version of Visual Studio (at least 2013) which includes the Microsoft Foundation Classes libraries. These instructions will - work for Visual Studio 2017 Community or Professional, both of which - include the MFC libraries if the "Visual C++ MFC" checkbox is - selected after enabling the "Desktop development with C++" workload. - If you do not plan to build the graphical ticket manager - application, the MFC libraries are not required. + work for Visual Studio 2022 Community or Professional. Include + the following components: -* A version of Perl. + - Under Workloads, select Desktop development with C++ + - Under Individual components -> SDKs, libraries, and frameworks, + select "C++ MFC for latest v*** build tools (x86 & x64)". This + component is not required if you do not wish to build the + graphical ticket manager. + - Under Individual components -> Compilers, build tools, and + runtimes, select "C++ 20** Redistributable MSMs". This component + is not required if you do not wish to build the installer. + +* An OpenSSL installation, including the headers, DLLs, and import + .LIB files. This dependency is optional if you are not building + PKINIT. We recommend building OpenSSL from source code. + +* A version of Perl. We recommend Strawberry Perl as it will work + to build OpenSSL. * Some common Unix utilities such as sed/awk/cp/cat installed in the command-line path. @@ -28,20 +39,12 @@ To build Kerberos 5 on Windows, you will need the following: A simple way to get the necessary Unix utilities is to install Git BASH from https://gitforwindows.org and configure it to add the Unix -utilities to the command-line path. In some versions of Windows (not -the most current versions), the Unix utilities can alternatively be -obtained via the Utilities and SDK for UNIX-based Applications, which -may be enabled as a Windows feature and then the components installed. -Note that the Windows nmake will not find the SUA awk utility in the -path unless it is named awk.exe; the permissions on the utility may -need correcting if awk.exe is created as a copy of the original awk. - -Git BASH contains a version of Perl, which will work to build krb5 if -the newlines in the source tree are not translated to native newlines. -Strawberry Perl will work regardless of whether newlines are -translated. If both Git BASH and Strawberry Perl are installed, you -may need to adjust the command line path to ensure that the preferred -Perl appears first. +utilities to the command-line path. + +Git BASH contains a version of Perl, which will work to build krb5, +but not to build OpenSSL from source. If both Git BASH and Strawberry +Perl are installed, you may need to adjust the command line path to +ensure that the preferred Perl appears first when building OpenSSL. The krb5 source tree may be obtained either directly on the Windows machine with a native git client cloning the krb5 public mirror at @@ -75,6 +78,14 @@ the MSI installer, this directory should be a temporary staging area in or near your build tree. The directory must exist before nmake install is run. +Set the environment variable OPENSSL_DIR to point to the root of the +OpenSSL install tree, and the environment variable OPENSSL_VERSION to +the version string as it apears in the DLL names (such as "1_1" or +"3"). Include files should be in %OPENSSL_DIR%\include, import .LIB +files should be in %OPENSSL_DIR%\lib, and the libcrypto DLL should be +in %OPENSSL_DIR%\bin\libcrypto-%OPENSSL_VERSION%-x64.dll. These steps +are optional if you do not wish to build PKINIT. + To skip building the graphical ticket manager, run "set NO_LEASH=1" before building, and do not build the installer. @@ -82,11 +93,14 @@ Run the following commands in a Visual Studio command prompt: 1) set PATH=%PATH%;"%WindowsSdkVerBinPath%"\x86 # To get uicc.exe 2) set KRB_INSTALL_DIR=\path\to\dir # Where bin/include/lib lives - 3) cd xxx\src # Go to where source lives - 4) nmake [NODEBUG=1] # Build the sources - 5) nmake install [NODEBUG=1] # Copy libraries/executables - 6) cd windows\installer\wix # Go to the installer source - 7) nmake [NODEBUG=1] # Build the installer + 3) set OPENSSL_DIR=\path\to\openssl # Where OpenSSL lives + 4) set OPENSSL_VERSION=3 # Version of OpenSSL DLLs + 5) cd xxx\src # Go to where source lives + 6) nmake -f Makefile.in prep-windows # Create Makefile for Windows + 7) nmake [NODEBUG=1] # Build the sources + 8) nmake install [NODEBUG=1] # Copy libraries/executables + 9) cd windows\installer\wix # Go to the installer source +10) nmake [NODEBUG=1] # Build the installer Step 1 may be skipped if uicc is already in the command-line path (try running "uicc" to see if you get a usage message or a not-found diff --git a/src/windows/installer/wix/config.wxi b/src/windows/installer/wix/config.wxi index ea6e610a0..ffd68d9cd 100644 --- a/src/windows/installer/wix/config.wxi +++ b/src/windows/installer/wix/config.wxi @@ -38,6 +38,8 @@ + + diff --git a/src/windows/installer/wix/features.wxi b/src/windows/installer/wix/features.wxi index 3405dc482..de99ad7ed 100644 --- a/src/windows/installer/wix/features.wxi +++ b/src/windows/installer/wix/features.wxi @@ -63,7 +63,9 @@ + + diff --git a/src/windows/installer/wix/files.wxi b/src/windows/installer/wix/files.wxi index 805856eae..675eea803 100644 --- a/src/windows/installer/wix/files.wxi +++ b/src/windows/installer/wix/files.wxi @@ -272,6 +272,9 @@ + + + @@ -305,8 +308,12 @@ + + + + diff --git a/src/windows/installer/wix/platform.wxi b/src/windows/installer/wix/platform.wxi index 006e35544..470400aa1 100644 --- a/src/windows/installer/wix/platform.wxi +++ b/src/windows/installer/wix/platform.wxi @@ -73,6 +73,10 @@ + + + +