Initial revision
authorJonathan Kamens <jik@mit.edu>
Fri, 12 Nov 1993 17:02:48 +0000 (17:02 +0000)
committerJonathan Kamens <jik@mit.edu>
Fri, 12 Nov 1993 17:02:48 +0000 (17:02 +0000)
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@2902 dc483132-0cff-0310-8789-dd5450dbe970

doc/kadm5/api-unit-test.tex [new file with mode: 0644]

diff --git a/doc/kadm5/api-unit-test.tex b/doc/kadm5/api-unit-test.tex
new file mode 100644 (file)
index 0000000..be9ab27
--- /dev/null
@@ -0,0 +1,1706 @@
+\documentstyle[times,fullpage,rcsid]{article}
+
+\rcs$Header$
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Make _ actually generate an _, and allow line-breaking after it.
+\let\underscore=\_
+\catcode`_=13
+\def_{\underscore\penalty75\relax}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\newcommand{\test}[1]{\begin{description}
+#1
+\end{description}
+
+}
+%\setlength{\parskip}{\baselineskip}
+\newcommand{\Reason}[1]{\item[Reason:] #1}
+%\newcommand{\Call}[1]{\item[Call:] #1}
+%\newcommand{\Expected}[1]{\item[Expected:] #1}
+\newcommand{\Conditions}[1]{\item[Conditions:] #1}
+
+%\newcommand{\Reason}[1]{}
+\newcommand{\Call}[1]{}
+\newcommand{\Expected}[1]{}
+%\newcommand{\Conditions}[1]{}
+
+\title{OpenV*Secure 1.0 Admin API\\
+Unit Test Description\footnote{\rcsHeader}}
+\author{Jonathan I. Kamens}
+
+\begin{document}
+
+\maketitle
+
+%\tableofcontents
+
+\section{Introduction}
+
+The following is a description of a black-box unit test of the
+OpenV*Secure Admin API.  Each API function is listed, followed by the
+tests that shoud be performed on it.
+
+The tests described here are based on the ``OV*Secure Admin Functional
+Specifications'' dated November 9, 1993.
+
+Since inter-realm functionality is not a requirement for OpenV*Secure
+1.0, it is not tested.
+
+%In these tests: ``usera'' and ``userb'' (abbreviated ``a'' and ``b'') as the
+%non-realm part of a principal represent the names of principals that
+%exist in the current realm; ``nouser'' (abbreviated ``n'') represents a
+%principal that does not exist in the current realm; ``useras-password''
+%(abbreviated ``a's-p'') represents ``usera'''s password; ``userbs-password''
+%(abbreviated ``b's-p'') represents ``userb'''s password; ``no-password''
+%(abbreviated ``no-p'') represents some password string which isn't the
+%password of anyone in the database; ``LOCAL.REALM'' (abbreviated ``L.R'')
+%represents the local realm; and ``BAD.REALM'' (abbreviated ``B.R'')
+%represents a nonexistent realm.
+
+All tests which test for success should verify, using some means other
+than the return value of the function being tested, that the requested
+operation was successfully performed.  For example: for init, test
+that other operations can be performed after init; for destroy, test
+that other operations can't be performed after destroy; for modify
+functions, verify that all modifications to the database which should
+have taken place did, and that the new, modified data is in effect;
+for get operations, verify that the data retrieved is the data that
+should actually be in the database.
+
+Similarly, all tests which test for failure should verify that the
+no component of the requested operation took place.  For example: if
+init fails, other operations should not work.  If a modify fails, all
+data in the database should be the same as it was before the attempt
+to modify, and the old data should still be what is enforced.
+Furthermore, tests which test for failure should verify that the
+failure code returned is correct for the specific failure condition
+tested.
+
+\section{ovsec_kadm_init}
+
+%ADMIN_SERVICE is abbreviated A_S, and CHANGEPW_SERVICE is abbreviated
+%C_S.
+
+\test{
+\Reason{An empty string realm is rejected.}
+\Call{ovsec_kadm_init(a, a's-p, A_S, "")}
+\Expected{returns XXX.}
+}
+
+\test{
+\Reason{A bad realm is rejected.}
+\Call{ovsec_kadm_init(a, a's-p, A_S, B.R)}
+\Expected{returns XXX}
+}
+
+\test{
+\Reason{A bad service name representing an existing principal
+               is rejected.}
+\Call{ovsec_kadm_init(a, a's-p, b, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{A bad service name representing a non-existent
+               principal is rejected.}
+\Call{ovsec_kadm_init(a, a's-p, n, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{A bad service name identical to the (existing) client
+               name is rejected.}
+\Call{ovsec_kadm_init(a, a's-p, a, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{A null password is rejected.}
+\Call{ovsec_kadm_init(a, null, A_S, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{An empty-string password is rejected.}
+\Call{ovsec_kadm_init(a, "", A_S, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{An incorrect password which is the password of another
+               user is rejected.}
+\Call{ovsec_kadm_init(a, b's-p, A_S, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{An incorrect password which isn't the password of any
+               user is rejected.}
+\Call{ovsec_kadm_init(a, no-p, A_S, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{A null client_name is rejected.}
+\Call{ovsec_kadm_init(null, no-p, A_S, null)}
+\Expected{returns XXX}
+}
+
+\test{
+\Reason{An empty-string client_name is rejected.}
+\Call{ovsec_kadm_init("", no-p, A_S, null)}
+\Expected{returns XXX}
+}
+
+\test{
+\Reason{A client_name referring to a non-existent principal in
+               the default realm is rejected.}
+\Call{ovsec_kadm_init(n, no-p, A_S, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{A client_name referring to a non-existent principal
+               with the local realm specified explicitly is rejected.}
+\Call{ovsec_kadm_init(n@L.R, no-p, A_S, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{A client_name referring to a non-existent principal in
+               a bad realm is rejected.}
+\Call{ovsec_kadm_init(n@B.R, no-p, A_S, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{A client_name referring to an existing principal in a
+               bad realm is rejected.}
+\Call{ovsec_kadm_init(a@B.R, a's-p, A_S, null)}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{Valid invocation.}
+\Call{ovsec_kadm_init(a, a-s'p, A_S, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+}
+
+\test{
+\Reason{Valid invocation (explicit client realm).}
+\Call{ovsec_kadm_init(a@L.R, a-s'p, A_S, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+}
+
+\test{
+\Reason{Valid invocation (CHANGEPW_SERVICE).}
+\Call{ovsec_kadm_init(a, a-s'p, C_S, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+}
+
+\test{
+\Reason{Valid invocation (explicit service realm).}
+\Call{ovsec_kadm_init(a, a-s'p, A_S, L.R);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+}
+
+\test{
+\Reason{Valid invocation (database access allowed after init).}
+\Call{ovsec_kadm_init(a, a-s'p, A_S, null);
+               ovsec_kadm_get_principal(a, buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+}
+
+\test{
+\Reason{Init fails when called twice in a row.}
+\Call{ovsec_kadm_init(a, a-s'p, A_S, null);
+               ovsec_kadm_init(a, a-s'p, A_S, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK the first time, XXX the second time}
+}
+
+\test{
+\Reason{Null password is ignored in local invocation.}
+\Call{ovsec_kadm_init(a, null, A_S, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+\Conditions{local}
+}
+
+\test{
+\Reason{Non-null password is ignored in local invocation.}
+\Call{ovsec_kadm_init(a, no-p, A_S, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+\Conditions{local}
+}
+
+\test{
+\Reason{Null service name is ignored in local invocation.}
+\Call{ovsec_kadm_init(a, null, null, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+\Conditions{local}
+}
+
+\test{
+\Reason{Non-null service name is ignored in local invocation.}
+\Call{ovsec_kadm_init(a, null, n, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+\Conditions{local}
+}
+
+\section{ovsec_kadm_destroy}
+
+\test{
+\Reason{Valid invocation.}
+\Call{ovsec_kadm_init(a, a-s'p, A_S, null);
+               ovsec_kadm_destroy()}
+\Expected{returns OK}
+}
+
+\test{
+\Reason{Valid invocation (``get'' not allowed after destroy).}
+}
+
+\test{
+\Reason{Valid invocation (``add'' not allowed after destroy).}
+}
+
+\test{
+\Reason{Valid invocation (``modify'' not allowed after destroy).}
+}
+
+\test{
+\Reason{Valid invocation (``delete'' not allowed after destroy).}
+}
+
+\test{
+\Reason{Fails if database not initialized.}
+\Call{ovsec_kadm_destroy()}
+\Expected{returns NOT_INIT}
+}
+
+\test{
+\Reason{Fails if invoked twice in a row.}
+\Call{ovsec_kadm_init(a, a's-p, A_S, null);
+               ovsec_kadm_destroy();
+               ovsec_kadm_destroy()}
+\Expected{returns OK the first time, NOT_INIT the second}
+}
+
+\test{
+\Reason{Database can be reinitialized after destroy.}
+\Call{ovsec_kadm_init(a, a's-p, A_S, null);
+               ovsec_kadm_destroy();
+               ovsec_kadm_init(a, a's-p, A_S, null);
+               ovsec_kadm_get_principal(a, buffer);
+               verify contents of buffer;
+               ovsec_kadm_destroy()}
+}
+
+\section{ovsec_kadm_create_principal}
+
+%In the tests below, ``getu'' refers to a user who has only ``get'' access,
+%''addu'' refers to a user who has only ``add'' access, ``modifyu'' refers to
+%a user who has only ``modify'' access, and ``deleteu'' refers to a user
+%who has only ``delete'' access. ``amu'' refers to a user with ``add'' and
+%''modify'' access.  ``new_princ'' refers to a principal entry structure
+%filled in as follows:
+%
+%      krb5_parse_name("newuser", \&new_princ.principal);
+%      krb5_timeofday(\&new_princ.princ_expire_time);
+%              new_princ.princ_expire_time += 130;
+%      krb5_timeofday(\&new_princ.last_pwd_change);
+%              new_princ.last_pwd_change += 140;
+%      krb5_timeofday(\&new_princ.pw_expiration);
+%              new_princ.pw_expiration += 150;
+%      new_princ.max_life = 160;
+%      krb5_parse_name("usera", \&new_princ.mod_name);
+%      krb5_timeofday(\&new_princ.mod_date);
+%              new_princ.mod_date += 170;
+%      new_princ.attributes = 0xabcdabcd;
+%      new_princ.kvno = 180;
+%      new_princ.mkvno = 190;
+%      new_princ.policy = null;
+%      new_princ.aux_attributes = 0xdeadbeef;
+%
+%The offsets of 130 through 190 above are used to ensure that the
+%fields are all known to be different from each other, so that
+%accidentally switched fields can be detected.  Some of the fields in
+%this structure may be changed by the tests, but they should clean up
+%after themselves.
+
+\test{
+\Reason{Fails if database not initialized.}
+}
+
+\test{
+\Reason{Fails on null princ argument.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(null, PRINCIPAL, "foobar",
+                                           true);
+               ovsec_kadm_destroy()}
+\Expected{returns EINVAL}
+}
+
+\test{
+\Reason{Fails on null password argument.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL, null,
+                                           true);
+               ovsec_kadm_destroy()}
+\Expected{returns EINVAL}
+}
+
+\test{
+\Reason{Fails on empty-string password argument. XXX Assumes
+               that an empty string is not a legal password.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL, "",
+                                           true);
+               ovsec_kadm_destroy()}
+\Expected{returns XXX}
+}
+
+\test{
+\Reason{Fails when mask contains undefined bit.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL | 0x002000,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns BAD_MASK}
+}
+
+\test{
+\Reason{Fails when mask contains LAST_PWD_CHANGE bit.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, 
+                                           PRINCIPAL | LAST_PWD_CHANGE,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns BAD_MASK}
+}
+
+\test{
+\Reason{Fails when mask contains MOD_TIME bit.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL | MOD_TIME,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns BAD_MASK}
+}
+
+\test{
+\Reason{Fails when mask contains MOD_NAME bit.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL | MOD_NAME,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns BAD_MASK}
+}
+
+\test{
+\Reason{Fails when mask contains MKVNO bit.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL | MKVNO,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns BAD_MASK}
+}
+
+\test{
+\Reason{Fails when mask contains AUX_ATTRIBUTES bit.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ,
+                                           PRINCIPAL | AUX_ATTRIBUTES,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns BAD_MASK}
+}
+
+\test{
+\Reason{Fails when mask contains POLICY_CLR bit.}
+\Call{ovsec_kadm_init(addu, addu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL | POLICY_CLR,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns BAD_MASK}
+}
+
+\test{
+\Reason{Fails for caller with no access bits.}
+}
+
+\test{
+\Reason{Fails when caller has ``get'' access and not ``add''.}
+\Call{ovsec_kadm_init(getu, getu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns AUTH_ADD}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{Fails when caller has ``modify'' access and not ``add''.}
+\Call{ovsec_kadm_init(modifyu, modifyu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns AUTH_ADD}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{Fails when caller has ``delete'' access and not ``add''.}
+\Call{ovsec_kadm_init(deleteu, deleteu's-p, A_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns AUTH_ADD}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{Fails when caller connected with CHANGEPW_SERVICE.}
+\Call{ovsec_kadm_init(addu, addu's-p, C_S, null);
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL,
+                                           "foobar", true);
+               ovsec_kadm_get_principal("newuser", buffer);
+               ovsec_kadm_destroy()}
+\Expected{returns XXX}
+\Conditions{RPC}
+}
+
+\test{
+\Reason{Fails on attempt to create existing principal.}
+\Call{ovsec_kadm_init(getu, getu's-p, A_S, null);
+               ovsec_kadm_get_principal("usera", buffer);
+               ovsec_kadm_destroy();
+               ovsec_kadm_init(addu, addu's-p, A_S, null);
+               save new_princ's principal;
+               new_princ.principal = buffer.principal;
+               save new_princ's max_life;
+               new_princ.max_life = buffer.max_life + 1;
+               ovsec_kadm_create_principal(new_princ, PRINCIPAL,
+                                           "foobar", true);
+               ovsec_kadm_destroy();
+               ovsec_kadm_init(getu, getu's-p, A_S, null);
+               ovsec_kadm_get_principal("usera", buffer2);
+               ovsec_kadm_destroy()
+               compare buffer to buffer2;
+               restore new_princ's principal;
+               restore new_princ's max_life;
+               ovsec_kadm_free_principle_ent(buffer);
+               ovsec_kadm_free_principal_ent(buffer2)}
+\Expected{returns DUP}
+}
+
+\test{
+\Reason{Fails when password is too short.}
+}
+
+\test{
+\Reason{Fails when password has too few classes.}
+}
+
+\test{
+\Reason{Fails when password is in dictionary.}
+}
+
+\test{
+\Reason{Nonexistent policy is rejected.}
+}
+
+\test{
+\Reason{Fails on invalid principal name.}
+}
+
+\test{
+\Reason{Valid invocation.}
+}
+
+\test{
+\Reason{Succeeds when caller has ``add'' access and another one.}
+}
+
+\test{
+\Reason{Allows too-short password when override_qual is true.}
+}
+
+\test{
+\Reason{Allows password with too few classes when
+               override_qual is true.}
+}
+
+\test{
+\Reason{Allows password in dictionary when override_qual is
+               true.}
+}
+
+\test{
+\Reason{Succeeds when assigning policy.}
+}
+
+\test{
+\Reason{Allows 0 (never) for princ_expire_time.}
+}
+
+\test{
+\Reason{Allows 0 (never) for pw_expiration when there's no policy.}
+}
+
+\test{
+\Reason{Allows 0 (never) for pw_expiration when there's a policy with
+       0 for pw_max_life.}
+}
+
+\test{
+\Reason{Accepts 0 (never) for pw_expiration when there's a policy with
+       non-zero pw_max_life, but actually sets pw_expiration to now +
+       pw_max_life.}
+}
+
+\test{
+\Reason{Accepts and sets non-zero pw_expiration when no policy.}
+}
+
+\test{
+\Reason{Accepts and sets non-zero pw_expiration when there's a policy
+       with zero pw_max_life.}
+}
+
+\test{
+\Reason{Accepts and sets non-zero pw_expiration when there's a policy
+       with pw_max_life later than the specified pw_expiration.}
+}
+
+\test{
+\Reason{Accepts non-zero pw_expiration and limits it to now +
+       pw_max_life when it's later than now + non-zero pw_max_life in
+       policy.}
+}
+
+\test{
+\Reason{Sets pw_expiration to 0 (never) if there's no policy and no
+       specified pw_expiration.}
+}
+
+\test{
+\Reason{Sets pw_expiration to 0 (never) if it isn't specified and the
+       policy has a 0 (never) pw_max_life.}
+}
+
+\test{
+\Reason{Sets pw_expiration to now + pw_max_life if it isn't specified
+       and the policy has a non-zero pw_max_life.}
+}
+
+\test{
+\Reason{Allows 0 (forever) for max_life.}
+}
+
+
+
+\section{ovsec_kadm_delete_principal}
+
+\test{
+\Reason{Fails if database not initialized.}
+}
+
+\test{
+\Reason{Fails on null principal.}
+}
+
+\test{
+\Reason{Fails on empty-string principal.}
+}
+
+\test{
+\Reason{Fails on invalid principal name.}
+}
+
+\test{
+\Reason{Fails on nonexistent principal.}
+}
+
+\test{
+\Reason{Fails when caller connected with CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Fails if caller has ``add'' access and not ``delete''.}
+}
+
+\test{
+\Reason{Fails if caller has ``modify'' access and not ``delete''.}
+}
+
+\test{
+\Reason{Fails if caller has ``get'' access and not ``delete''.}
+}
+
+\test{
+\Reason{Fails if caller has no access bits.}
+}
+
+\test{
+\Reason{Valid invocation.}
+\Expected{Principal is removed from database.}
+}
+
+\test{
+\Reason{Valid invocation (on principal with policy).}
+\Expected{Principal is removed from database.  Reference count
+               of its policy is decremented.}
+}
+
+
+
+\section{ovsec_kadm_modify_principal}
+
+\test{
+\Reason{Fails if database not initialized.}
+}
+
+\test{
+\Reason{Fails if user connected with CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Fails on mask with undefined bit set.}
+}
+
+\test{
+\Reason{Fails on mask with PRINCIPAL set.}
+}
+
+\test{
+\Reason{Fails on mask with LAST_PWD_CHANGE set.}
+}
+
+\test{
+\Reason{Fails on mask with MOD_TIME set.}
+}
+
+\test{
+\Reason{Fails on mask with MOD_NAME set.}
+}
+
+\test{
+\Reason{Fails on mask with MKVNO set.}
+}
+
+\test{
+\Reason{Fails on mask with AUX_ATTRIBUTES set.}
+}
+
+\test{
+\Reason{Fails on nonexistent principal.}
+}
+
+\test{
+\Reason{Fails for user with no access bits.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' access.}
+}
+
+\test{
+\Reason{Fails for user with ``add'' access.}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' access.}
+}
+
+\test{
+\Reason{Succeeds for user with ``modify'' access.}
+}
+
+\test{
+\Reason{Succeeds for user with ``modify'' and another access.}
+}
+
+\test{
+\Reason{Fails when nonexistent policy is specified.}
+}
+
+\test{
+\Reason{Succeeds when existent policy is specified.}
+}
+
+\test{
+\Reason{Updates policy count when setting policy from none.}
+}
+
+\test{
+\Reason{Updates policy count when clearing policy from set.}
+}
+
+\test{
+\Reason{Updates policy count when setting policy from other policy.}
+}
+
+\test{
+\Reason{Allows 0 (never) for pw_expiration when there's no policy.}
+}
+
+\test{
+\Reason{Allows 0 (never) for pw_expiration when there's a policy with
+       0 for pw_max_life.}
+}
+
+\test{
+\Reason{Accepts 0 (never) for pw_expiration when there's a policy with
+       non-zero pw_max_life, but actually sets pw_expiration to
+       last_pwd_change + pw_max_life.}
+}
+
+\test{
+\Reason{Accepts and sets non-zero pw_expiration when no policy.}
+}
+
+\test{
+\Reason{Accepts and sets non-zero pw_expiration when there's a policy
+       with zero pw_max_life.}
+}
+
+\test{
+\Reason{Accepts and sets non-zero pw_expiration when there's a policy
+       with pw_max_life later than the specified pw_expiration.}
+}
+
+\test{
+\Reason{Accepts non-zero pw_expiration and limits it to last_pwd_change +
+       pw_max_life when it's later than last_pwd_change + non-zero
+       pw_max_life in policy.}
+}
+
+\test{
+\Reason{Sets pw_expiration to 0 (never) if there's no policy and no
+       specified pw_expiration.}
+}
+
+\test{
+\Reason{Sets pw_expiration to 0 (never) if it isn't specified and the
+       policy has a 0 (never) pw_max_life.}
+}
+
+\test{
+\Reason{Sets pw_expiration to now + pw_max_life if it isn't specified
+       and the policy has a non-zero pw_max_life.}
+}
+
+\test{
+\Reason{Accepts princ_expire_time change.}
+}
+
+\test{
+\Reason{Accepts attributes change.}
+}
+
+\test{
+\Reason{Accepts max_life change.}
+}
+
+\test{
+\Reason{Accepts kvno change.}
+}
+
+\test{
+\Reason{Behaves correctly when policy is set to the same as it was
+       before.}
+}
+
+\test{
+\Reason{Behaves properly when POLICY_CLR is specified and there was no
+       policy before.}
+}
+
+\test{
+\Reason{Accepts 0 (never) for princ_expire_time.}
+}
+
+\test{
+\Reason{Accepts 0 for max_life.}
+}
+
+
+
+\section{ovsec_kadm_rename_principal}
+
+\test{
+\Reason{Fails if database not initialized.}
+}
+
+\test{
+\Reason{Fails if user connected with CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with no access bits.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'' access and not ``add'' or
+``delete''.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' access and not ``add'' or
+``delete''.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'' and ``add'' but not ``delete''.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'' and ``delete'' but not ``add''.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' and ``add'' but not ``delete''.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' and ``delete'' but not ``add.''}
+}
+
+\test{
+\Reason{Fails for user with ``modify'', ``get'' and ``add'', but not
+       ``delete''.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'', ``get'' and ``delete'', but
+       not ``add''.}
+}
+
+\test{
+\Reason{Fails for user with ``add'' but not ``delete''.}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``add''.}
+}
+
+\test{
+\Reason{Succeeds for user with ``add'' and ``delete''.}
+}
+
+\test{
+\Reason{Fails if target principal name exists.}
+}
+
+
+
+\section{ovsec_kadm_chpass_principal}
+\label{ovseckadmchpassprincipal}
+
+\subsection{Quality/history enforcement tests}
+
+This section lists a series of tests which will be run a number of
+times, with various parameter settings (e.g., which access bits user
+has, whether user connected with ADMIN_SERVICE or CHANGEPW_SERVICE,
+whether override_qual is specified, etc.).  These changes should
+either all succeed or all fail, depending on the parameter settings.
+After the list of tests, the various invocations of them, with the
+corresponding parameter settings and whether the changes should
+succeed or fail, will be given.
+
+\subsubsection{List of tests}
+
+\test{
+\Reason{With history setting of 1, change password to itself.}
+}
+
+\test{
+\Reason{With history setting of 2 but no password changes since
+       principal creation, change password to itself.}
+}
+
+\test{
+\Reason{With history setting of 2 and one password change since
+       principal creation, change password to itself
+       and directly previous password.}
+}
+
+\test{
+\Reason{With a history setting of 3 and no password changes,
+       change password to itself.}
+}
+
+\test{
+\Reason{With a history setting of 3 and 1 password change,
+       change password itself or previous password.}
+}
+
+\test{
+\Reason{With a history setting of 3 and 2 password changes,
+       change password to itself and the two previous passwords.}
+}
+
+\test{
+\Reason{Change to previously unused password when now -
+       last_pwd_change $<$ pw_min_life.}
+}
+
+\test{
+\Reason{Change to previously unused password that doesn't contain enough
+       character classes.}
+}
+
+\test{
+\Reason{Change to previously unused password that's too short.}
+}
+
+\test{
+\Reason{Change to previously unused password that's in the dictionary.}
+}
+
+\subsubsection{List of parameter settings}
+
+\begin{tabular}{lllll}
+Modify access? & Own password? & Service & override_qual & Pass/Fail \\ \hline
+no & yes & ADMIN & false & fail \\
+no & yes & ADMIN & true & RPC: fail; local: {\em pass} \\
+no & yes & CHANGEPW & false & fail \\
+no & yes & CHANGEPW & true & RPC: fail; local: {\em pass} \\
+no & no & ADMIN & false & fail \\
+no & no & ADMIN & true & RPC: fail; local: {\em pass} \\
+no & no & CHANGEPW & false & fail \\
+no & no & CHANGEPW & true & RPC: fail; local: {\em pass} \\
+yes & yes & ADMIN & false & fail \\
+yes & yes & ADMIN & true & RPC: fail; local {\em pass} \\
+yes & yes & CHANGEPW & false & fail \\
+yes & yes & CHANGEPW & true & RPC: fail; local: {\em pass} \\
+yes & no & ADMIN & false & fail \\
+yes & no & ADMIN & true & {\em pass} \\
+yes & no & CHANGEPW & false & fail \\
+yes & no & CHANGEPW & true & RPC: fail; local: {\em pass}
+\end{tabular}
+
+\subsection{Other quality/history tests}
+
+These tests should be run with override_qual false.
+
+\test{
+\Reason{With history of 1, can change password to anything other than
+       itself that doesn't conflict with other quality
+       rules.}
+}
+
+\test{
+\Reason{With history of 2 and 2 password changes, can change password
+       to original password.}
+}
+
+\test{
+\Reason{With history of 3 and 3 password changes, can change password
+       to original password.}
+}
+
+\test{
+\Reason{Can change password when now - last_pwd_change $>$ pw_min_life.}
+}
+
+\test{
+\Reason{Can change password when it contains exactly the number of
+       classes required by the policy.}
+}
+
+\test{
+\Reason{Can change password when it is exactly the length required by
+       the policy.}
+}
+
+\test{
+\Reason{Can change password to a word that isn't in the dictionary.}
+}
+
+
+\subsection{Other tests}
+
+\test{
+\Reason{Fails if database not initialized.}
+}
+
+\test{
+\Reason{Fails for non-existent principal.}
+}
+
+\test{
+\Reason{Fails for null password.}
+}
+
+\test{
+\Reason{Fails for empty-string password.}
+}
+
+\test{
+\Reason{Pw_expiration is set to now + max_pw_life if policy exists and
+       has non-zero max_pw_life.}
+}
+
+\test{
+\Reason{Pw_expiration is set to 0 if policy exists and has zero
+       max_pw_life.}
+}
+
+\test{
+\Reason{Pw_expiration is set to 0 if no policy.}
+}
+
+\test{
+\Reason{KRB5_KDC_REQUIRES_PWCHANGE bit is cleared when password is
+       successfully changed.}
+}
+
+\test{
+\Reason{Fails for user with no access bits, on other's password.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' but not ``modify'' access, on
+       other's password.}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``modify'' access, on
+       other's password.}
+}
+
+\test{
+\Reason{Fails for user with ``add'' but not ``modify'' access, on
+       other's password.}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'' and ``modify'' access, on
+       other's password.}
+}
+
+
+
+\section{ovsec_kadm_chpass_principal_util}
+
+Rerun all the tests listed for ovsec_kadm_chpass_principal above in
+Section \ref{ovseckadmchpassprincipal}.  Verify that they succeed
+and fail in the same circumstances.  Also verify that in each failure
+case, the error message returned in msg_ret is as specified in the
+functional specification.
+
+
+
+\section{ovsec_kadm_randkey_principal}
+
+\subsection{TOOSOON enforcement tests}
+
+This test should be run a number of times, as indicated in the table
+following it.  The table also indicates the expected result of each
+run of the test.
+
+\test{
+\Reason{Change key when now - last_pwd_change $<$ pw_min_life.}
+}
+
+\subsubsection{List of parameter settings}
+
+\begin{tabular}{lllll}
+Modify access? & Own key? & Service & override_qual & Pass/Fail \\ \hline
+no & yes & ADMIN & false & fail \\
+no & yes & ADMIN & true & RPC: fail; local: {\em pass} \\
+no & yes & CHANGEPW & false & fail \\
+no & yes & CHANGEPW & true & RPC: fail; local: {\em pass} \\
+no & no & ADMIN & false & fail \\
+no & no & ADMIN & true & RPC: fail; local: {\em pass} \\
+no & no & CHANGEPW & false & fail \\
+no & no & CHANGEPW & true & RPC: fail; local: {\em pass} \\
+yes & yes & ADMIN & false & fail \\
+yes & yes & ADMIN & true & RPC: fail; local {\em pass} \\
+yes & yes & CHANGEPW & false & fail \\
+yes & yes & CHANGEPW & true & RPC: fail; local: {\em pass} \\
+yes & no & ADMIN & false & fail \\
+yes & no & ADMIN & true & {\em pass} \\
+yes & no & CHANGEPW & false & fail \\
+yes & no & CHANGEPW & true & RPC: fail; local: {\em pass}
+\end{tabular}
+
+\subsection{Other tests}
+
+\test{
+\Reason{Fails if database not initialized.}
+}
+
+\test{
+\Reason{Fails for non-existent principal.}
+}
+
+\test{
+\Reason{Fails for null keyblock pointer.}
+}
+
+\test{
+\Reason{Pw_expiration is set to now + max_pw_life if policy exists and
+       has non-zero max_pw_life.}
+}
+
+\test{
+\Reason{Pw_expiration is set to 0 if policy exists and has zero
+       max_pw_life.}
+}
+
+\test{
+\Reason{Pw_expiration is set to 0 if no policy.}
+}
+
+\test{
+\Reason{KRB5_KDC_REQUIRES_PWCHANGE bit is cleared when key is
+       successfully changed.}
+}
+
+\test{
+\Reason{Fails for user with no access bits, on other's password.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' but not ``modify'' access, on
+       other's password.}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``modify'' access, on
+       other's password.}
+}
+
+\test{
+\Reason{Fails for user with ``add'' but not ``modify'' access, on
+       other's password.}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'' and ``modify'' access, on
+       other's password.}
+}
+
+\test{
+\Reason{The new key that's assigned is truly random. XXX not sure how
+       to test this.}
+}
+
+
+
+\section{ovsec_kadm_get_principal}
+
+\test{
+\Reason{Fails for null ent.}
+}
+
+\test{
+\Reason{Fails for non-existent principal.}
+}
+
+\test{
+\Reason{Fails for user with no access bits, retrieving other principal.}
+}
+
+\test{
+\Reason{Fails for user with ``add'' but not ``get'', getting principal
+       other than his own, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'' but not ``get'', getting
+       principal other than his own, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``get'', getting
+       principal other than his own, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``get'', getting
+       principal other than his own, using CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``get'', getting principal other than his
+       own, using CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user without ``get'', retrieving self, using
+       ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user without ``get'', retrieving self, using
+       CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'', retrieving self, using
+       ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'', retrieving self, using
+       CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'', retrieving other user, using
+       ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'' and ``modify'', retrieving
+       other principal, using ADMIN_SERVICE.}
+}
+
+
+
+\section{ovsec_kadm_create_policy}
+
+\test{
+\Reason{Fails for mask with undefined bit set.}
+}
+
+\test{
+\Reason{Fails if caller connected with CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Fails for mask without POLICY bit set.}
+}
+
+\test{
+\Reason{Fails for mask with REF_COUNT bit set.}
+}
+
+\test{
+\Reason{Fails for invalid policy name.}
+}
+
+\test{
+\Reason{Fails for existing policy name.}
+}
+
+\test{
+\Reason{Fails for null policy name.}
+}
+
+\test{
+\Reason{Fails for empty-string policy name.}
+}
+
+\test{
+\Reason{Accepts 0 for pw_min_life.}
+}
+
+\test{
+\Reason{Accepts non-zero for pw_min_life.}
+}
+
+\test{
+\Reason{Accepts 0 for pw_max_life.}
+}
+
+\test{
+\Reason{Accepts non-zero for pw_max_life.}
+}
+
+\test{
+\Reason{Accepts 0 for pw_min_length.}
+}
+
+\test{
+\Reason{Accepts non-zero for pw_min_length.}
+}
+
+\test{
+\Reason{Rejects 0 for pw_min_classes.}
+}
+
+\test{
+\Reason{Accepts 1 for pw_min_classes.}
+}
+
+\test{
+\Reason{Accepts 4 for pw_min_classes.}
+}
+
+\test{
+\Reason{Rejects 5 for pw_min_classes.}
+}
+
+\test{
+\Reason{Rejects 0 for pw_history_num.}
+}
+
+\test{
+\Reason{Accepts 1 for pw_history_num.}
+}
+
+\test{
+\Reason{Accepts 10 for pw_history_num.}
+}
+
+\test{
+\Reason{Fails for user with no access bits.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' but not ``add''.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'' but not ``add.''}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``add.''}
+}
+
+\test{
+\Reason{Succeeds for user with ``add.''}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'' and ``add.''}
+}
+
+
+
+\section{ovsec_kadm_delete_policy}
+
+\test{
+\Reason{Fails for null policy name.}
+}
+
+\test{
+\Reason{Fails for empty-string policy name.}
+}
+
+\test{
+\Reason{Fails for non-existent policy name.}
+}
+
+\test{
+\Reason{Fails for bad policy name.}
+}
+
+\test{
+\Reason{Fails if caller connected with CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with no access bits.}
+}
+
+\test{
+\Reason{Fails for user with ``add'' but not ``delete''.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'' but not ``delete''.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' but not ``delete.''}
+}
+
+\test{
+\Reason{Succeeds for user with only ``delete''.}
+}
+
+\test{
+\Reason{Succeeds for user with ``delete'' and ``add''.}
+}
+
+\test{
+\Reason{Fails for policy with non-zero reference count.}
+}
+
+
+
+\section{ovsec_kadm_modify_policy}
+
+\test{
+\Reason{Fails for mask with undefined bit set.}
+}
+
+\test{
+\Reason{Fails if caller connected with CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Fails for mask with POLICY bit set.}
+}
+
+\test{
+\Reason{Fails for mask with REF_COUNT bit set.}
+}
+
+\test{
+\Reason{Fails for invalid policy name.}
+}
+
+\test{
+\Reason{Fails for non-existent policy name.}
+}
+
+\test{
+\Reason{Fails for null policy name.}
+}
+
+\test{
+\Reason{Fails for empty-string policy name.}
+}
+
+\test{
+\Reason{Accepts 0 for pw_min_life.}
+}
+
+\test{
+\Reason{Accepts non-zero for pw_min_life.}
+}
+
+\test{
+\Reason{Accepts 0 for pw_max_life.}
+}
+
+\test{
+\Reason{Accepts non-zero for pw_max_life.}
+}
+
+\test{
+\Reason{Accepts 0 for pw_min_length.}
+}
+
+\test{
+\Reason{Accepts non-zero for pw_min_length.}
+}
+
+\test{
+\Reason{Rejects 0 for pw_min_classes.}
+}
+
+\test{
+\Reason{Accepts 1 for pw_min_classes.}
+}
+
+\test{
+\Reason{Accepts 4 for pw_min_classes.}
+}
+
+\test{
+\Reason{Rejects 5 for pw_min_classes.}
+}
+
+\test{
+\Reason{Rejects 0 for pw_history_num.}
+}
+
+\test{
+\Reason{Accepts 1 for pw_history_num.}
+}
+
+\test{
+\Reason{Accepts 10 for pw_history_num.}
+}
+
+\test{
+\Reason{Fails for user with no access bits.}
+}
+
+\test{
+\Reason{Fails for user with ``get'' but not ``modify''.}
+}
+
+\test{
+\Reason{Fails for user with ``add'' but not ``modify.''}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``modify.''}
+}
+
+\test{
+\Reason{Succeeds for user with ``modify.''}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'' and ``modify.''}
+}
+
+
+
+\section{ovsec_kadm_get_policy}
+
+\test{
+\Reason{Fails for null policy.}
+}
+
+\test{
+\Reason{Fails for invalid policy name.}
+}
+
+\test{
+\Reason{Fails for empty-string policy name.}
+}
+
+\test{
+\Reason{Fails for non-existent policy name.}
+}
+
+\test{
+\Reason{Fails for null ent.}
+}
+
+\test{
+\Reason{Fails for user with no access bits trying to get other's
+       policy, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``add'' but not ``get'' trying to get
+       other's policy, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'' but not ``get'' trying to get
+       other's policy, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``get'' trying to get
+       other's policy, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``delete'' but not ``get'' trying to get
+       other's policy, using CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with only ``get'', trying to get own policy,
+       using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with only ``get'', trying to get own policy,
+       using CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with ``add'' and ``get'', trying to get own
+       policy, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with ``add'' and ``get'', trying to get own
+       policy, using CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user without ``get'', trying to get own policy,
+       using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user without ``get'', trying to get own policy,
+       using CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with ``get'', trying to get other's policy,
+       using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``get'', trying to get other's policy,
+       using CHANGEPW_SERVICE.}
+}
+
+\test{
+\Reason{Succeeds for user with ``modify'' and ``get'', trying to get
+       other's policy, using ADMIN_SERVICE.}
+}
+
+\test{
+\Reason{Fails for user with ``modify'' and ``get'', trying to get
+       other's policy, using CHANGEPW_SERVICE.}
+}
+
+
+
+\section{ovsec_kadm_free_principal_ent}
+
+Handled by memory-leak testing handled elsewhere.
+
+
+
+\section{ovsec_kadm_free_policy_ent}
+
+Handled by memory-leak testing handled elsewhere.
+
+
+
+\section{ovsec_kadm_get_privs}
+
+
+This test should be run with \test{
+\Reason{Fails for null pointer argument.}
+}
+
+the 16 possible combinations of access
+bits (since there are 4 access bits, there are $2^4 = 16$ popsible
+combinations of them):
+
+\test{
+\Reason{Returns correct bit mask for access bits of user.}
+\Conditions{RPC}
+}
+
+This test should be run locally:
+
+\test{
+\Reason{Returns 0x0f.}
+\Conditions{local}
+}
+
+\end{document}