From: Tom Yu Date: Sat, 2 Aug 2014 18:20:35 +0000 (-0400) Subject: Tests for unlocked iteration X-Git-Tag: krb5-1.13-alpha1~24 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=344d6e26d9a11303a6078ae6afeb5fc8c4b42865;p=thirdparty%2Fkrb5.git Tests for unlocked iteration ticket: 7977 --- diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in index abd431d072..34816e3866 100644 --- a/src/tests/Makefile.in +++ b/src/tests/Makefile.in @@ -51,6 +51,10 @@ t_init_creds: t_init_creds.o $(KRB5_BASE_DEPLIBS) t_localauth: t_localauth.o $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o $@ t_localauth.o $(KRB5_BASE_LIBS) +unlockiter: unlockiter.o $(KDB5_DEPLIBS) $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ unlockiter.o $(KDB5_LIBS) $(KADMSRV_LIBS) \ + $(KRB5_BASE_LIBS) + check-unix:: kdb_check kdc.conf: Makefile @@ -94,7 +98,7 @@ kdb_check: kdc.conf krb5.conf $(RM) $(TEST_DB)* stash_file check-pytests:: gcred hist hrealm kdbtest plugorder rdreq responder s2p -check-pytests:: t_init_creds t_localauth +check-pytests:: t_init_creds t_localauth unlockiter $(RUNPYTEST) $(srcdir)/t_general.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_dump.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_iprop.py $(PYTESTFLAGS) @@ -136,6 +140,7 @@ check-pytests:: t_init_creds t_localauth $(RUNPYTEST) $(srcdir)/t_bogus_kdc_req.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_kdc_log.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_proxy.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_unlockiter.py $(PYTESTFLAGS) clean:: $(RM) gcred hist hrealm kdbtest plugorder rdreq responder s2p diff --git a/src/tests/t_unlockiter.py b/src/tests/t_unlockiter.py new file mode 100644 index 0000000000..d4ca4c5a6c --- /dev/null +++ b/src/tests/t_unlockiter.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +from k5test import * + +# Default KDB iteration is locked. Expect write lock failure unless +# unlocked iteration is explicitly requested. +realm = K5Realm() +realm.run(['./unlockiter'], expected_code=1) +realm.run(['./unlockiter', '-u']) +realm.run(['./unlockiter', '-l'], expected_code=1) + +# Set default to unlocked iteration. Only explicitly requested locked +# iteration should block the write lock. +realm = K5Realm(krb5_conf={'dbmodules': {'db': {'unlockiter': 'true'}}}) +realm.run(['./unlockiter']) +realm.run(['./unlockiter', '-u']) +realm.run(['./unlockiter', '-l'], expected_code=1) + +success('Unlocked iteration unit tests') diff --git a/src/tests/unlockiter.c b/src/tests/unlockiter.c new file mode 100644 index 0000000000..e2f2eb5eda --- /dev/null +++ b/src/tests/unlockiter.c @@ -0,0 +1,273 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* tests/unlockiter.c - test program for unlocked iteration */ +/* + * Copyright (C) 2014 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. + */ + +/* + * Test unlocked KDB iteration. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +struct cb_arg { + int inpipe; + int outpipe; + int timeout; + int done; +}; + +/* Helper function for cb(): read a sync byte (with possible timeout), then + * write a sync byte. */ +static int +syncpair_rw(const char *name, struct cb_arg *arg, char *cp, int timeout) +{ + struct timeval tv; + fd_set rset; + int nfds; + + FD_ZERO(&rset); + FD_SET(arg->inpipe, &rset); + tv.tv_sec = timeout; + tv.tv_usec = 0; + + printf("cb: waiting for %s sync pair\n", name); + nfds = select(arg->inpipe + 1, &rset, + NULL, NULL, (timeout == 0) ? NULL : &tv); + if (nfds < 0) + return -1; + if (nfds == 0) { + errno = ETIMEDOUT; + return -1; + } + if (read(arg->inpipe, cp, 1) < 0) + return -1; + printf("cb: writing %s sync pair\n", name); + if (write(arg->outpipe, cp, 1) < 0) + return -1; + return 0; +} + +/* On the first iteration only, receive and send sync bytes to the locking + * child to drive its locking activities. */ +static krb5_error_code +cb(void *argin, krb5_db_entry *ent) +{ + struct cb_arg *arg = argin; + char c = '\0'; + + if (arg->done) + return 0; + + if (syncpair_rw("first", arg, &c, 0) < 0) { + com_err("cb", errno, "first sync pair"); + return errno; + } + if (syncpair_rw("second", arg, &c, arg->timeout) < 0) { + com_err("cb", errno, "second sync pair"); + return errno; + } + printf("cb: waiting for final sync byte\n"); + if (read(arg->inpipe, &c, 1) < 0) { + com_err("cb", errno, "final sync byte"); + return errno; + } + arg->done = 1; + return 0; +} + +/* Parent process: iterate over the KDB, using a callback that synchronizes + * with the locking child. */ +static int +iterator(struct cb_arg *cb_arg, char **db_args, pid_t child) +{ + krb5_error_code retval; + krb5_context ctx; + + retval = krb5_init_context_profile(NULL, KRB5_INIT_CONTEXT_KDC, &ctx); + if (retval) + goto cleanup; + + retval = krb5_db_open(ctx, db_args, + KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN); + if (retval) + goto cleanup; + + retval = krb5_db_iterate(ctx, NULL, cb, cb_arg, 0); + if (retval) + goto cleanup; + + retval = krb5_db_fini(ctx); + +cleanup: + if (retval) { + com_err("iterator", retval, ""); + kill(child, SIGTERM); + exit(1); + } + return retval; +} + +/* Helper function for locker(): write, then receive a sync byte. */ +static int +syncpair_wr(const char *name, int inpipe, int outpipe, unsigned char *cp) +{ + printf("locker: writing %s sync pair\n", name); + if (write(outpipe, cp, 1) < 0) + return -1; + printf("locker: waiting for %s sync pair\n", name); + if (read(inpipe, cp, 1) < 0) + return -1; + return 0; +} + +/* Child process: acquire and release a write lock on the KDB, synchronized + * with parent. */ +static int +locker(int inpipe, int outpipe, char **db_args) +{ + krb5_error_code retval; + unsigned char c = '\0'; + krb5_context ctx; + + retval = krb5_init_context_profile(NULL, KRB5_INIT_CONTEXT_KDC, &ctx); + if (retval) + goto cleanup; + + retval = krb5_db_open(ctx, db_args, + KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN); + if (retval) + goto cleanup; + + if (syncpair_wr("first", inpipe, outpipe, &c) < 0) { + retval = errno; + goto cleanup; + } + printf("locker: acquiring lock...\n"); + retval = krb5_db_lock(ctx, KRB5_DB_LOCKMODE_EXCLUSIVE); + if (retval) + goto cleanup; + printf("locker: acquired lock\n"); + if (syncpair_wr("second", inpipe, outpipe, &c) < 0) { + retval = errno; + goto cleanup; + } + krb5_db_unlock(ctx); + printf("locker: released lock\n"); + printf("locker: writing final sync byte\n"); + if (write(outpipe, &c, 1) < 0) { + retval = errno; + goto cleanup; + } + retval = krb5_db_fini(ctx); +cleanup: + if (retval) + com_err("locker", retval, ""); + + krb5_free_context(ctx); + exit(retval != 0); +} + +static void +usage(const char *prog) +{ + fprintf(stderr, "usage: %s [-lu] [-t timeout]\n", prog); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct cb_arg cb_arg; + pid_t child; + char *db_args[2] = { NULL, NULL }; + int c; + int cstatus; + int pipe_to_locker[2], pipe_to_iterator[2]; + + cb_arg.timeout = 1; + cb_arg.done = 0; + while ((c = getopt(argc, argv, "lt:u")) != -1) { + switch (c) { + case 'l': + db_args[0] = "lockiter"; + break; + case 't': + cb_arg.timeout = atoi(optarg); + break; + case 'u': + db_args[0] = "unlockiter"; + break; + default: + usage(argv[0]); + } + } + if (pipe(pipe_to_locker) < 0) { + com_err(argv[0], errno, "pipe(p_il)"); + exit(1); + } + if (pipe(pipe_to_iterator) < 0) { + com_err(argv[0], errno, "pipe(p_li)"); + exit(1); + } + cb_arg.inpipe = pipe_to_iterator[0]; + cb_arg.outpipe = pipe_to_locker[1]; + child = fork(); + switch (child) { + case -1: + com_err(argv[0], errno, "fork"); + exit(1); + break; + case 0: + locker(pipe_to_locker[0], pipe_to_iterator[1], db_args); + break; + default: + if (iterator(&cb_arg, db_args, child)) + exit(1); + if (wait(&cstatus) < 0) { + com_err(argv[0], errno, "wait"); + exit(1); + } + if (WIFSIGNALED(cstatus)) + exit(1); + if (WIFEXITED(cstatus) && WEXITSTATUS(cstatus) != 0) { + exit(1); + } + } + exit(0); +}