api-server-design.tex   [plain text]


\documentstyle[12pt,fullpage,rcsid]{article}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Make _ actually generate an _, and allow line-breaking after it.
\let\underscore=\_
\catcode`_=13
\def_{\underscore\penalty75\relax}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\rcs$Id: api-server-design.tex 17363 2005-08-29 19:22:52Z hartmans $

\setlength{\parskip}{.7\baselineskip}
\setlength{\parindent}{0pt}

\def\v#1{\verb+#1+}
\def\k#1{K$_#1$}

\title{KADM5 Library and Server \\ Implementation Design\thanks{\rcsId}}
\author{Barry Jaspan}

\begin{document}

\sloppy
\maketitle

{\setlength{\parskip}{0pt}\tableofcontents}

\section{Overview}

The KADM5 administration system is designed around the KADM5 API.  The
``server-side'' library libkadm5srv.a implements the KADM5 API by
operating directly on the underlying KDC and admin databases.  The
``client-side'' library libkadm5clnt.a implements the KADM5 API via an
RPC mechanism.  The administration server kadmind accepts RPC requests
from the client-side library and translates them into calls to the
server-side library, performing authentication, authorization, and
logging along the way.

The two libraries, libkadm5clnt.a and libkadm5srv.a, export the
identical kadm5 interface; for example, both contain definitions for
kadm5_get_principal, and all other kadm5 functions.  In most cases,
the client library function just marshalls arguments and results into
and out of an RPC call, whereas the server library function performs
the actual operation on the database file.  kadm5_init_*, however, are
substantially different even though they export the same interface: on
the client, they establish the RPC connection and GSS-API context,
whereas on the server side the open the database files, read in the
password dictionary, and the like.  Also, the kadm5_free functions
operate on local process memory in both libraries.

The admin server is implemented as a nearly-stateless transaction
server, where each admin API function represents a single transaction.
No per-client or per-connection information is stored; only local
database handles are maintained between requests.  The RPC mechanism
provides access to remote callers' authentication credentials for
authorization purposes.

The admin API is exported via an RPC interface that hides all details
about network encoding, authentication, and encryption of data on the
wire.  The RPC mechanism does, however, allow the server to access the
underlying authentication credentials for authorization purposes.

The admin system maintains two databases:
%
\begin{itemize}
\item The master Kerberos (KDC) database is used to store all the
information that the Kerberos server understands, thus allowing the
greatest functionality with no modifications to a standard KDC.  

\item The KDC database also stores kadm5-specific per-principal
information in each principal's krb5_tl_data list.  In a prior
version, this data was stored in a separate admin principal database;
thus, when this document refers to ``the admin principal database,''
it now refers to the appropriate krb5_tl_data entries in the KDC
database.

\item The policy database stores kadm5 policy information.
\end{itemize}

The per-principal information stored in the admin principal database
consists of the principal's policy name and an array of the
principal's previous keys.  The old keys are stored encrypted in the
key of the special principal ``kadmin/history'' that is created by the
server library when it is first needed.  Since a change in
kadmin/history's key renders every principal's key history array
useless, it can only be changed using the ovsec_adm_edit utility; that
program will reencrypt every principal's key history in the new
key.\footnote{ovsec_adm_edit has not yet been implemented, and there
are currently no plans to implement it; thus, the history cannot
currently be changed.}  The server library refuses all requests to
change kadmin/history's key.

\section{API Handles}

Each call to kadm5_init_* on the client or server creates a new API
handle.  The handles encapsulate the API and structure versions
specified by kadm5_init_*'s caller and all other internal data needed
by the library.  A process can have multiple open API handles
simultaneously by calling kadm5_init_* multiple times, and call can
specify a different version, client or service principal, and so
forth.

Each kadm5 function verifies the handle it is given with the
CHECK_HANDLE or _KADM5_CHECK_HANDLE macros.  The CHECK_HANDLE macro
differs for the client and server library because the handle types
used by those libraries differ, so it is defined in both
$<$client_internal.h$>$ and $<$server_internal.h$>$ in the library
source directory.  In each header file, CHECK_HANDLE first calls
GENERIC_CHECK_HANDLE, defined in $<$admin_internal.h$>$, which
verifies the magic number, API version, and structure version that is
contained in both client and server handles.  CHECK_HANDLE then calls
either CLIENT_CHECK_HANDLE or SERVER_CHECK_HANDLE respectively to
verify the client- or server-library specific handle fields.

The CHECK_HANDLE macro is useful because it inlines the handle check
instead of requiring a separate function call.  However, using
CHECK_HANDLE means that a source file cannot be compiled once and
included into both the client and server library, because CHECK_HANDLE
is always either specific to either the client or server library, not
both.  There are a number of functions that can be implemented with
the same code in both the client and server libraries, however,
including all of the kadm5_free functions and
kadm5_chpass_principal_util.  The _KADM5_CHECK_HANDLE macro solves
this problem; instead of inlining the handle check, it calls the
function _kadm5_check_handle which is defined separately in both the
client and server library, in client_init.c and server_init.c.
Since these two files are only compiled once and put in a single
library, they simply verify the handle they are passed with
CHECK_HANDLE and return the result.

\section{API Versioning}

The KADM5 system is designed to support multiple versions of the KADM5
API.  Presently, two versions exist: KADM5_API_VERSION_1 and
KADM5_API_VERSION_2.  The former is equivalant to the initial
OpenVision API, OVSEC_KADM_API_VERSION_1; the latter was created
during the initial integration of the OpenVision system into the MIT
release.

Implementing a versioned API in C via with both local and RPC access
presents a number of design issues, some of them quite subtle.  The
contexts in which versioning considerations must be made include:

\begin{enumerate}
\item Typedefs, function declarations, and defined constants depend on
the API version a client is written to and must be correct at compile
time.

\item Each function in the server library must behave according to the
API version specified by the caller at runtime to kadm5_init_*.

\item The XDR functions used by the RPC layer to transmit function
arguments and results must encode data structures correctly depending
on the API version specified by the client at runtime.

\item Each function in the client library must behave according to the
API version specified by the caller at runtime to kadm5_init_*.

\item The RPC server (kadmind) must accept calls from a client using
any supported API version, and must then invoke the function in the
server library corresponding to the RPC with the API version indicated
by the client caller.

\item When a first API function is invoked that needs to call a second
function in the API on its own behalf, and that second API function's
behavior depends on the API version specified, the first API function
must either be prepared to call the second API function at whatever
version its caller specifies or have a means of always calling the
second API function at a pre-determined version.
\end{enumerate}

The following functions describe how each context is handled.

\subsection{Designing for future compatibility}

Any code whose behavior depends on the API version should be written
so as to be compatible with future, currently unknown API versions on
the grounds that any particuarly piece of API behavior will most
likely not change between versions.  For example, in the current
system, the code is not written as ``if this is VERSION_1, do X, else
if this is VERSION_2, do Y''; instead, it is written as ``if this is
VERSION_1, do X; else, do Y.''  The former will require additional
work when VERSION_3 is defined, even if ``do Y'' is still the correct
action, whereas the latter will work without modification in that
case.

\subsection{Header file declarations}

Typedefs, defined constants and macros, and function declarations may
change between versions.  A client is always written to a single,
specific API version, and thus expects the header files to define
everything according to that API.  Failure of a header file to define
values correctly will result in either compiler warnings (e.g. if the
pointer type of a function argument changes) or fatal errors (e.g. if
the number of arguments to a function changes, or the fields of a
structure change).  For example, in VERSION_1, kadm5_get_policy took a
pointer to a pointer to a structure, and in VERSION_2 it takes a
pointer to a structure; that would generate a warning if not correct.
In VERSION_1, kadm5_randkey_principal accepted three arguments but in
VERSION_2 accepts four; that would generate a fatal error.

The header file defines everything correctly based on the value of the
USE_KADM5_API_VERSION constant.  The constant can be assigned to an
integer corresponding to any supported API version, and defaults to
the newest version.  The header files then simply use an \#ifdef to
include the right definitions:
%
\begin{verbatim}
#if USE_KADM5_API_VERSION == 1
kadm5_ret_t    kadm5_get_principal(void *server_handle,
                                   krb5_principal principal,
                                   kadm5_principal_ent_t *ent);
#else
kadm5_ret_t    kadm5_get_principal(void *server_handle,
                                   krb5_principal principal,
                                   kadm5_principal_ent_t ent,
                                   long mask);
#endif
\end{verbatim}

\subsection{Server library functions}

Server library functions must know how many and what type of arguments
to expect, and must operate on those arguments correctly, based on the
API version with which they are invoked.  The API version is contained
in the handle that is alwasy passed as their first argument, generated
by kadm5_init_* (to which the client specified the API version to use
at run-time).

In general, it is probably unsafe for a compiled function in a library
to re-interpret the number and type of defined arguments at run-time
since the calling conventions may not allow it; for example, a
function whose first argument was a short in one version and a pointer
in the next might fail if it simply typed-casted the argument.  In
that case, the function would have to written to take variable
arguments (i.e. use $<$stdarg.h$>$) and extract them from the stack
based on the API version.  Alternatively, a separate function for each
API version could be defined, and $<$kadm5/admin.h$>$ could be written
to \v{\#define} the exported function name based on the value of
USE_KADM5_API_VERSION.

In the current system, it turns out, that isn't necessary, and future
implementors should take try to ensure that no version has semantics
that will cause such problems in the future.  All the functions in
KADM5 that have different arguments or results between VERSION_1 and
VERSION_2 do so simply by type-casting their arguments to the
appropriate version and then have separate code paths to handle each
one correctly.  kadm5_get_principal, in svr_principal.c, is a good
example.  In VERSION_1, it took the address of a pointer to a
kadm5_principal_ent_t to fill in with a pointer to allocated memory;
in VERSION_2, it takes a pointer to a structure to fill in, and a mask
of which fields in that structure should be filled in.  Also, the
contents of the kadm5_principal_ent_t changed slightly between the two
versions.  kadm5_get_principal handles versioning as follows
(following along in the source code will be helpful):

\begin{enumerate}
\item If VERSION_1, it saves away its entry argument (address of a
pointer to a structure) and resets its value to contain the address of
a locally stack-allocated entry structure; this allows most of the
function to written once, in terms of VERSION_2 semantics.  If
VERSION_1, it also resets its mask argument to be
KADM5_PRINCIPAL_NORMAL_MASK, because that is the equivalent to
VERSION_1 behavior, which was to return all the fields of the
structure.

\item The bulk of the function is implemented as expected for
VERSION_2.

\item The new fields in the VERSION_2 entry structure are assigned
inside a block that is only execute if the caller specified
VERSION_2.  This saves a little time for a VERSION_1 caller.

\item After the entry structure is filled, the function checks again
if it was called as VERSION_1.  If so, it allocates a new
kadm5_principal_ent_t_v1 structure (which is conveniently defined in
the header file) with malloc, copies the appropriate values from the
entry structure into the VERSION_1 entry structure, and then writes
the address of the newly allocated memory into address specified by
the original entry argument which it had previously saved away.
\end{enumerate}

There is another complication involved in a function re-interpreting
the number of arguments it receives at compile time---it cannot assign
any value to an argument for which the client did not pass a value.
For example, a VERSION_1 client only passes three arguments to
kadm5_get_principal.  If the implementation of kadm5_get_principal
notices that the caller is VERSION_1 and therefore assigns its fourth
argument, mask, to a value that mimics the VERSION_1 behavior, it may
inadvertently overwrite data on its caller's stack.  This problem can
be avoided simply by using a true local variable in such cases,
instead of treating an unpassed argument as a local variable.

\subsection{XDR functions}

The XDR functions used to encode function arguments and results must
know how to encode the data for any API version.  This is important
both so that all the data gets correctly transmitted and so that
protocol compatibility between clients or servers using the new
library but an old API version is maintained; specific, new kadmind
servers should support old kadm5 clients.

The signature of all XDR functions is strictly defined: they take the
address of an XDR function and the address of the data object to be
encoded or decoded.  It is thus impossible to provide the API version
of the data object as an additional argument to an XDR function.
There are two other means to convey the information, storing the API
version to use as a field in the data object itself and creating
separate XDR functions to handle each different version of the data
object, and both of them are used in KADM5.

In the client library, each kadm5 function collects its arguments into
a single structure to be passed by the RPC; similarly, it expects all
of the results to come back as a single structure from the RPC that it
will then decode back into its constituent pieces (these are the
standard ONC RPC semantics).  In order to pass versioning information
to the XDR functions, each function argument and result datatype has a
filed to store the API version.  For example, consider
kadm5_get_principal's structures:
%
\begin{verbatim}
struct gprinc_arg {
        krb5_ui_4 api_version;
        krb5_principal princ;
        long mask;
};
typedef struct gprinc_arg gprinc_arg;
bool_t xdr_gprinc_arg();

struct gprinc_ret {
        krb5_ui_4 api_version;
        kadm5_ret_t code;
        kadm5_principal_ent_rec rec;
};
typedef struct gprinc_ret gprinc_ret;
bool_t xdr_gprinc_ret();
\end{verbatim}
%
kadm5_get_principal (in client_principal.c) assigns the api_version
field of the gprinc_arg to the version specified by its caller,
assigns the princ field based on its arguments, and assigns the mask
field from its argument if the caller specified VERSION_2.  It then
calls the RPC function clnt_call, specifying the XDR functions
xdr_gprinc_arg and xdr_gprinc_ret to handle the arguments and results.

xdr_gprinc_arg is invoked with a pointer to the gprinc_arg structure
just described.  It first encodes the api_version field; this allows
the server to know what to expect.  It then encodes the krb5_principal
structure and, if api_version is VERSION_2, the mask.  If api_version
is not VERSION_2, it does not encode {\it anything} in place of the
mask, because an old VERSION_1 server will not expect any other data
to arrive on the wire there.

The server performs the kadm5_get_principal call and returns its
results in an XDR encoded gprinc_ret structure.  clnt_call, which has
been blocking until the results arrived, invokes xdr_gprinc_ret with a
pointer to the encoded data for it to decode.  xdr_gprinc_ret first
decodes the api_version field, and then the code field since that is
present in all versions to date.  The kadm5_principal_ent_rec presents
a problem, however.  The structure does not itself contain an
api_version field, but the structure is different between the two
versions.  Thus, a single XDR function cannot decode both versions of
the structure because it will have no way to decide which version to
expect.  The solution is to have two functions,
kadm5_principal_ent_rec_v1 and kadm5_principal_ent_rec, which always
decode according to VERSION_1 or VERSION_2, respectively.  gprinc_ret
knows which one to invoke because it has the api_version field
returned by the server (which is always the same as that specified by
the client in the gpring_arg).

In hindsight, it probably would have been better to encode the API
version of all structures directly in a version field in the structure
itself; then multiple XDR functions for a single data type wouldn't be
necessary, and the data objects would stand complete on their own.
This can be added in a future API version if desired.

\subsection{Client library functions}

Just as with server library functions, client library functions must
be able to interpret their arguments and provide result according to
the API version specified by the caller.  Again, kadm5_get_principal
(in client_principal.c) is a good example.  The gprinc_ret structure
that it gets back from clnt_call contains a kadm5_principal_ent_rec or
a kadm5_principal_ent_rec_v1 (the logic is simplified somewhat because
the VERSION_2 structure only has new fields added on the end).  If
kadm5_get_principal was invoked with VERSION_2, that structure should
be copied into the pointer provided as the entry argument; if it was
invoked with VERSION_1, however, the structure should be copied into
allocated memory whose address is then written into the pointer
provided by the entry argument.  Client library functions make this
determination based on the API version specified in the provided
handle, just like server library functions do.

\subsection{Admin server stubs}

When an RPC call arrives at the server, the RPC layer authenticates
the call using the GSS-API, decodes the arguments into their
single-structure form (ie: a gprinc_arg) and dispatches the call to a
stub function in the server (in server_stubs.c).  The stub function
first checks the caller's authorization to invoke the function and, if
authorized, calls the kadm5 function corresponding to the RPC function
with the arguments specified in the single-structure argument.

Once again, kadm5_get_principal is a good example for the issues
involved.  The contents of the gprinc_arg given to the stub
(get_principal_1) depends on the API version the caller on the client
side specified; that version is available to the server in the
api_version field of the gprinc_arg.  When the server calls
kadm5_get_principal in the server library, it must give that function
an API handle that contains the API version requested by the client;
otherwise the function semantics might not be correct.  One
possibility would be for the server to call kadm5_init for each client
request, specifing the client's API version number and thus generating
an API handle with the correct version, but that would be
prohibitively inefficient.  Instead, the server dips down in the
server library's internal abstraction barrier, using the function
new_server_handle to cons up a server handle based on the server's own
global_server_handle but using the API version specified by the
client.  The server then passes the newly generated handle to
kadm5_get_principal, ensuring the right behavior, and creates the
gprinc_ret structure in a manner similar to that described above.

Although new_server_handle solves the problem of providing the server
with an API handle containing the right API version number, it does
not solve another problem: that a single source file, server_stubs.c,
needs to be able to invoke functions with arguments appropriate for
multiple API versions.  If the client specifies VERSION_1, for
example, the server must invoke kadm5_get_principal with three
arguments, but if the client specifies VERSION_2 the server must
invoke kadm5_get_principal with four arguments.  The compiler will not
allow this inconsistency.  The server defines wrapper functions in a
separate source file that match the old version, and the separate
source file is compiled with USE_KADM5_API_VERSION set to the old
version; see kadm5_get_principal_v1 in server_glue_v1.c.  The server
then calls the correct variant of kadm5_get_principal_* based on the
API version and puts the return values into the gprinc_ret in a manner
similar to that described above.

Neither of these solutions are necessarily correct.  new_server_handle
violates the server library's abstraction barrier and is at best a
kludge; the server library should probably export a function to
provide this behavior without violating the abstraction;
alternatively, the librar should be modified so that having the server
call kadm5_init for each client RPC request would not be too
inefficient.  The glue functions in server_glue_v1.c really are not
necessary, because the server stubs could always just pass dummy
arguments for the extra arguments; after all, the glue functions pass
{\it nothing} for the extra arguments, so they just end up as stack
garbage anyway.

Another alternative to the new_server_handle problem is to have the
server always invoke server library functions at a single API version,
and then have the stubs take care of converting the function arguments
and results back into the form expected by the caller.  In general,
however, this might require the stubs to duplicate substantial logic
already present in the server library and further violate the server
library's abstraction barrier.

\subsection{KADM5 self-reference}

Some kadm5 functions call other kadm5 functions ``on their own
behalf'' to perform functionality that is necessary but that does not
directly affect what the client sees.  For example,
kadm5_chpass_principal has to enforce password policies; thus, it
needs to call kadm5_get_principal and, if the principal has a policy,
kadm5_get_policy and kadm5_modify_principal in the process of changing
a principal's password.  This leads to a complication: what API handle
should kadm5_chpass_principal pass to the other kadm5 functions it
calls?

The ``obvious,'' but wrong, answer is that it should pass the handle
it was given by its caller.  The caller may provide an API handle
specifying any valid API version.  Although the semantics of
kadm5_chpass_principal did not change between VERSION_1 and VERSION_2,
the declarations of both kadm5_get_principal and kadm5_get_policy
did.  Thus, to use the caller's API handle, kadm5_chpass_principal
will have to have a separate code path for each API version, even
though it itself did not change bewteen versions, and duplicate a lot
of logic found elsewhere in the library.

Instead, each API handle contains a ``local-use handle,'' or lhandle,
that kadm5 functions should use to call other kadm5 functions.  For
example, the client-side library's handle structure is:
%
\begin{verbatim}
typedef struct _kadm5_server_handle_t {
        krb5_ui_4       magic_number;
        krb5_ui_4       struct_version;
        krb5_ui_4       api_version;
        char *          cache_name;
        int             destroy_cache;
        CLIENT *        clnt;
        krb5_context    context;
        kadm5_config_params params;
        struct _kadm5_server_handle_t *lhandle;
} kadm5_server_handle_rec, *kadm5_server_handle_t;
\end{verbatim}
%
The lhandle field is allocated automatically when the handle is
created.  All of the fields of the API handle that are accessed
outside kadm5_init are also duplicated in the lhandle; however, the
api_version field of the lhandle is always set to a {\it constant}
value, regardless of the API version specified by the caller to
kadm5_init.  In the current implementation, the lhandle's api_version
is always VERSION_2.

By passing the caller's handle's lhandle to recursively called kadm5
functions, a kadm5 function is assured of invoking the second kadm5
function with a known API version.  Additionally, the lhandle's
lhandle field points back to the lhandle, in case kadm5 functions call
themselves more than one level deep; handle$->$lhandle always points
to the same lhandle, no matter how many times the indirection is
performed.

This scheme might break down if a kadm5 function has to call another
kadm5 function to perform operations that they client will see and for
its own benefit, since the semantics of the recursively-called kadm5
function may depend on the API version specified and the client may be
depending on a particular version's behavior.  Future implementators
should avoid creating a situation in which this is possible.

\section{Server Main}

The admin server starts by trapping all fatal signals and directing
them to a cleanup-and-exit function.  It then creates and exports the
RPC interface and enters its main loop.

The main loop dispatches all incoming requests to the RPC mechanism.
In a previous version, after 15 seconds of inactivity, the server
closed all open databases; each database was be automatically reopened
by the API function implementations as necessary.  That behavior
existed to protect against loss of written data before the process
exited.  The current database libraries write all changes out to disk
immediately, however, so this behavior is no longer required or
performed.

\section{Remote Procedure Calls}

The RPC for the Admin system will be based on ONC RPC.  ONC RPC is
used because it is a well-known, portable RPC mechanism.  The
underlying external data representation (xdr) mechanisms for wire
encapsulation are well-known and extensible.  Authentication to the
admin server and encryption of all RPC functional arguments and
results are be handled via the AUTH_GSSAPI authentication flavor of
ONC RPC.

\section{Database Record Types}
\label{sec:db-types}

\subsection{Admin Principal, osa_princ_ent_t}

The admin principal database stores records of the type
osa_princ_ent_t (declared in $<$kadm5/adb.h$>$), which is the
subset of the kadm5_principal_ent_t structure that is not stored
in the Kerberos database plus the necessary bookkeeping information.
The records are keyed by the ASCII representation of the principal's
name, including the trailing NULL.

\begin{verbatim}
typedef struct _osa_pw_hist_t {
     int n_key_data;
     krb5_key_data *key_data;
} osa_pw_hist_ent, *osa_pw_hist_t;

typedef struct _osa_princ_ent_t {
        char * policy;
        u_int32 aux_attributes;

        unsigned int old_key_len;
        unsigned int old_key_next;
        krb5_kvno admin_history_kvno;
        osa_pw_hist_ent *old_keys;


        u_int32 num_old_keys;
        u_int32 next_old_key;
        krb5_kvno admin_history_kvno;
        osa_pw_hist_ent *old_keys;
} osa_princ_ent_rec, *osa_princ_ent_t;
\end{verbatim}

The fields that are different from kadm5_principal_ent_t are:

\begin{description}
\item[num_old_keys] The number of previous keys in the old_keys array.
This value must be 0 $\le$ num_old_keys $<$ pw_history_num.

\item[old_key_next] The index into old_keys where the next key should
be inserted.  This value must be 0 $\le$ old_key_next $\le$
num_old_keys.

\item[admin_history_kvno] The key version number of the kadmin/history
principal's key used to encrypt the values in old_keys.  If the server
library finds that kadmin/history's kvno is different from the value
in this field, it returns KADM5_BAD_HIST_KEY.

\item[old_keys] The array of the principal's previous passwords, each
encrypted in the kadmin/history key.  There are num_old_keys
elements.  Each ``password'' in the array is itself an array of
n_key_data krb5_key_data structures, one for each keysalt type the
password was encoded in.
\end{description}

\subsection{Policy, osa_policy_ent_t}

The policy database stores records of the type osa_policy_ent_t
(declared in $<$kadm5/adb.h$>$) , which is all of
kadm5_policy_ent_t plus necessary bookkeeping information.  The
records are keyed by the policy name.

\begin{verbatim}
typedef struct _osa_policy_ent_t {
        char *policy;

        u_int32 pw_min_life;
        u_int32 pw_max_life;
        u_int32 pw_min_length;
        u_int32 pw_min_classes;
        u_int32 pw_history_num;

        u_int32 refcnt;
} osa_policy_ent_rec, *osa_policy_ent_t;
\end{verbatim}

\subsection{Kerberos, krb5_db_entry}

The Kerberos database stores records of type krb5_db_entry, which is
defined in the $<$k5-int.h$>$ header file.  The semantics of each
field are defined in the libkdb functional specification.

\section{Database Access Methods}

\subsection{Principal and Policy Databases}

This section describes the database abstraction used for the admin
policy database; the admin principal database used to be treated in
the same manner but is now handled more directly as krb5_tl_data;
thus, nothing in this section applies to it any more.  Since both
databases export equivalent functionality, the API is only described
once.  The character T is used to represent both ``princ'' and
``policy''. The location of the principal database is defined by the
configuration parameters given to any of the kadm5_init functions in
the server library.

Note that this is {\it only} a database abstraction.  All functional
intelligence, such as maintaining policy reference counts or sanity
checking, must be implemented above this layer.

Prototypes for the osa functions are supplied in
$<$kadm5/adb.h$>$. The routines are defined in libkadm5srv.a. They
require linking with the Berkely DB library.

\subsubsection{Error codes}

The database routines use com_err for error codes.  The error code
table name is ``adb'' and the offsets are the same as the order
presented here. The error table header file is
$<$kadm5/adb_err.h$>$. Callers of the OSA routines should first call
init_adb_err_tbl() to initialize the database table.

\begin{description}
\item[OSA_ADB_OK] Operation successful.
\item[OSA_ADB_FAILURE] General failure.
\item[OSA_ADB_DUP] Operation would create a duplicate database entry.
\item[OSA_ADB_NOENT] Named entry not in database.
\item[OSA_ADB_BAD_PRINC] The krb5_principal structure is invalid.
\item[OSA_ADB_BAD_POLICY] The specified policy name is invalid.
\item[OSA_ADB_XDR_FAILURE] The principal or policy structure cannot be
encoded for storage.
\item[OSA_ADB_BADLOCKMODE] Bad lock mode specified.
\item[OSA_ADB_CANTLOCK_DB] Cannot lock database, presumably because it
is already locked.
\item[OSA_ADB_NOTLOCKED] Internal error, database not locked when
unlock is called.
\item[OSA_ADB_NOLOCKFILE] KADM5 administration database lock file missing.
\end{description}

Database functions can also return system errors.  Unless otherwise
specified, database functions return OSA_ADB_OK.

\subsubsection{Locking}

All of the osa_adb functions except open and close lock and unlock the
database to prevent concurrency collisions.  The overall locking
algorithm is as follows:

\begin{enumerate}
\item osa_adb_open_T calls osa_adb_init_db to allocate the osa_adb_T_t
structure and open the locking file for further use.

\item Each osa_adb functions locks the locking file and opens the
appropriate database with osa_adb_open_and_lock, performs its action,
and then closes the database and unlocks the locking file with
osa_adb_close_and_unlock.

\item osa_adb_close_T calls osa_adb_fini_db to close the locking file
and deallocate the db structure.
\end{enumerate}

Functions which modify the database acquire an exclusive lock, others
acqure a shared lock.  osa_adb_iter_T acquires an exclusive lock for
safety but as stated below consequences of modifying the database in
the iteration function are undefined.

\subsubsection{Function descriptions}

\begin{verbatim}
osa_adb_ret_t osa_adb_create_T_db(kadm5_config_params *params)
\end{verbatim}
%
Create the database and lockfile specified in params.  The database
must not already exist, or EEXIST is returned.  The lock file is only
created after the database file has been created successfully.

\begin{verbatim}
osa_adb_ret_t osa_adb_rename_T_db(kadm5_config_params *fromparams,
				  kadm5_config_params *toparams)
\end{verbatim}
%
Rename the database named by fromparams to that named by toparams.
The fromparams database must already exist; the toparams database may
exist or not.  When the function returns, the database named by
fromparams no longer exists, and toparams has been overwritten with
fromparams.  This function acquires a permanent lock on both databases
for the duration of its operation, so a failure is likely to leave the
databases unusable.

\begin{verbatim}
osa_adb_ret_t osa_adb_destroy_policy_db(kadm5_config_params *params)
\end{verbatim}
%
Destroy the database named by params.  The database file and lock file
are deleted.

\begin{verbatim}
osa_adb_ret_t
osa_adb_open_T(osa_adb_T_t *db, char *filename);
\end{verbatim}
%
Open the database named filename.  Returns OSA_ADB_NOLOCKFILE if the
database does not exist or if the lock file is missing.  The database
is not actually opened in the operating-system file sense until a lock
is acquire.

\begin{verbatim}
osa_adb_ret_t
osa_adb_close_T(osa_adb_T_t db);
\end{verbatim}
%
Release all shared or exclusive locks (on BOTH databases, since they
use the same lock file) and close the database.

It is an error to exit while a permanent lock is held;
OSA_ADB_NOLOCKFILE is returned in this case.

\begin{verbatim}
osa_adb_ret_t osa_adb_get_lock(osa_adb_T_t db, int mode)
\end{verbatim}

Acquire a lock on the administration databases; note that both
databases are locked simultaneously by a single call.  The mode
argument can be OSA_ADB_SHARED, OSA_ADB_EXCLUSIVE, or
OSA_ADB_PERMANENT.  The first two and the third are really disjoint
locking semantics and should not be interleaved.

Shared and exclusive locks have the usual semantics, and a program can
upgrade a shared lock to an exclusive lock by calling the function
again.  A reference count of open locks is maintained by this function
and osa_adb_release_lock so the functions can be called multiple
times; the actual lock is not released until the final
osa_adb_release_lock.  Note, however, that once a lock is upgraded
from shared to exclusive, or from exclusive to permanent, it is not
downgraded again until released completely.  In other words,
get_lock(SHARED), get_lock(EXCLUSIVE), release_lock() leaves the
process with an exclusive lock with a reference count of one.  An
attempt to get a shared or exclusive lock that conflicts with another
process results in the OSA_ADB_CANLOCK_DB error code.

This function and osa_adb_release_lock are called automatically as
needed by all other osa_adb functions to acquire shared and exclusive
locks and so are not normally needed.  They can be used explicitly by
a program that wants to perform multiple osa_adb functions within the
context of a single lock.

Acquiring an OSA_ADB_PERMANENT lock is different.  A permanent lock
consists of first acquiring an exclusive lock and then {\it deleting
the lock file}.  Any subsequent attempt to acquire a lock by a
different process will fail with OSA_ADB_NOLOCKFILE instead of
OSA_ADB_CANTLOCK_DB (attempts in the same process will ``succeed''
because only the reference count gets incremented).  The lock file is
recreated by osa_adb_release_lock when the last pending lock is released.

The purpose of a permanent lock is to absolutely ensure that the
database remain locked during non-atomic operations.  If the locking
process dies while holding a permanent lock, all subsequent osa_adb
operations will fail, even through a system reboot.  This is useful,
for example, for ovsec_adm_import which creates both new database
files in a temporary location and renames them into place.  If both
renames do not fully complete the database will probably be
inconsistent and everything should stop working until an administrator
can clean it up.

\begin{verbatim}
osa_adb_ret_t osa_adb_release_lock(osa_adb_T_t db)
\end{verbatim}

Releases a shared, exclusive, or permanent lock acquired with
osa_adb_get_lock, or just decrements the reference count if multiple
locks are held.  When a permanent lock is released, the lock file is
re-created.

All of a process' shared or exclusive database locks are released when
the process terminates.  A permanent lock is {\it not} released when
the process exits (although the exclusive lock it begins with
obviously is).

\begin{verbatim}
osa_adb_ret_t
osa_adb_create_T(osa_adb_T_t db, osa_T_ent_t entry);
\end{verbatim}
%
Adds the entry to the database.  All fields are defined.  Returns
OSA_ADB_DUP if it already exists.

\begin{verbatim}
osa_adb_ret_t
osa_adb_destroy_T(osa_adb_T_t db, osa_T_t name);
\end{verbatim}

Removes the named entry from the database.  Returns OSA_ADB_NOENT if
it does not exist.

\begin{verbatim}
osa_adb_ret_t
osa_adb_get_T(osa_adb_T_t db, osa_T_t name,
        osa_princ_ent_t *entry); 
\end{verbatim}

Looks up the named entry in the db, and returns it in *entry in
allocated storage that must be freed with osa_adb_free_T.  Returns
OSA_ADB_NOENT if name does not exist, OSA_ADB_MEM if memory cannot be
allocated.

\begin{verbatim}
osa_adb_ret_t
osadb_adb_put_T(osa_adb_T_t db, osa_T_ent_t entry);
\end{verbatim}

Modifies the existing entry named in entry.  All fields must be filled
in.  Returns OSA_DB_NOENT if the named entry does not exist.  Note
that this cannot be used to rename an entry; rename is implemented by
deleting the old name and creating the new one (NOT ATOMIC!).

\begin{verbatim}
void osa_adb_free_T(osa_T_ent_t);
\end{verbatim}

Frees the memory associated with an osa_T_ent_t allocated by
osa_adb_get_T.

\begin{verbatim}
typedef osa_adb_ret_t (*osa_adb_iter_T_func)(void *data,
                                    osa_T_ent_t entry);

osa_adb_ret_t osa_adb_iter_T(osa_adb_T_t db, osa_adb_iter_T_func func, 
                    void *data);
\end{verbatim}

Iterates over every entry in the database.  For each entry ent in the
database db, the function (*func)(data, ent) is called.  If func
returns an error code, osa_adb_iter_T returns an error code.  If all
invokations of func return OSA_ADB_OK, osa_adb_iter_T returns
OSA_ADB_OK.  The function func is permitted to access the database,
but the consequences of modifying the database during the iteration
are undefined.

\subsection{Kerberos Database}

Kerberos uses the libkdb interface to store krb5_db_entry records.  It
can be accessed and modified in parallel with the Kerberos server,
using functions that are defined inside the KDC and the libkdb.a.  The
libkdb interface is defined in the libkdb functional specifications.

\subsubsection{Initialization and Key Access}

Keys stored in the Kerberos database are encrypted in the Kerberos
master key.  The admin server will therefore have to acquire the key
before it can perform any key-changing operations, and will have to
decrypt and encrypt the keys retrieved from and placed into the
database via krb5_db_get_principal and _put_principal.  This section
describes the internal admin server API that will be used to perform
these functions.

\begin{verbatim}
krb5_principal master_princ;
krb5_encrypt_block master_encblock;
krb5_keyblock master_keyblock;

void kdc_init_master()
\end{verbatim}

kdc_init_master opens the database and acquires the master key.  It
also sets the global variables master_princ, master_encblock, and
master_keyblock:

\begin{itemize}
\item master_princ is set to the name of the Kerberos master principal
(\v{K/M@REALM}).

\item master_encblock is something I have no idea about.

\item master_keyblock is the Kerberos master key
\end{itemize}

\begin{verbatim}
krb5_error_code kdb_get_entry_and_key(krb5_principal principal,
                                      krb5_db_entry *entry,
                                      krb5_keyblock *key)
\end{verbatim}

kdb_get_entry_and_key retrieves the named principal's entry from the
database in entry, and decrypts its key into key.  The caller must
free entry with krb5_dbm_db_free_principal and free key-$>$contents with
free.\footnote{The caller should also \v{memset(key-$>$contents, 0,
key-$>$length)}.  There should be a function krb5_free_keyblock_contents
for this, but there is not.}

\begin{verbatim}
krb5_error_code kdb_put_entry_pw(krb5_db_entry *entry, char *pw)
\end{verbatim}

kdb_put_entry_pw stores entry in the database.  All the entry values
must already be set; this function does not change any of them except
the key.  pw, the NULL-terminated password string, is converted to a
key using string-to-key with the salt type specified in
entry-$>$salt_type.\footnote{The salt_type should be set based on the
command line arguments to the kadmin server (see the ``Command Line''
section of the functional specification).}

\section{Admin Principal and Policy Database Implementation}

The admin principal and policy databases will each be stored in a
single hash table, implemented by the Berkeley 4.4BSD db library.
Each record will consist of an entire osa_T_ent_t.  The key into the
hash table is the entry name (for principals, the ASCII representation
of the name).  The value is the T entry structure.  Since the key and
data must be self-contained, with no pointers, the Sun xdr mechanisms
will be used to marshal and unmarshal data in the database.

The server in the first release will be single-threaded in that a
request will run to completion (or error) before the next will run,
but multiple connections will be allowed simultaneously.

\section{ACLs, acl_check}

The ACL mechanism described in the ``Authorization ACLs'' section of
the functional specifications will be implemented by the acl_check
function.

\begin{verbatim}
enum access_t {
        ACCESS_DENIED = 0,
        ACCESS_OK = 1,
};

enum access_t acl_check(krb5_principal princ, char *priv);
\end{verbatim}

The priv argument must be one of ``get'', ``add'', ``delete'', or
``modify''.  acl_check returns 1 if the principal princ has the named
privilege, 0 if it does not.

\section{Function Details}

This section discusses specific design issues for Admin API functions
that are not addresed by the functional specifications.

\subsection{kadm5_create_principal}

If the named principal exists in either the Kerberos or admin
principal database, but not both, return KADM5_BAD_DB.

The principal's initial key is not stored in the key history array at
creation time.

\subsection{kadm5_delete_principal}

If the named principal exists in either the Kerberos or admin
principal database, but not both, return KADM5_BAD_DB.

\subsection{kadm5_modify_principal}

If the named principal exists in either the Kerberos or admin
principal database, but not both, return KADM5_BAD_DB.

If pw_history_num changes and the new value $n$ is smaller than the
current value of num_old_keys, old_keys should end up with the $n$
most recent keys; these are found by counting backwards $n$ elements
in old_keys from old_key_next.  old_key_nexts should then be reset to
0, the oldest of the saved keys, and num_old_keys set to $n$, the
new actual number of old keys in the array.  

\subsection{kadm5_chpass_principal, randkey_principal}

The algorithm for determining whether a password is in the principal's
key history is complicated by the use of the kadmin/history \k{h}
encrypting key.  

\begin{enumerate}
\item For kadm5_chpass_principal, convert the password to a key
using string-to-key and the salt method specified by the command line
arguments.

\item If the POLICY bit is set and pw_history_num is not zero, check
if the new key is in the history.
\begin{enumerate}
\item Retrieve the principal's current key and decrypt it with \k{M}.
If it is the same as the new key, return KADM5_PASS_REUSE.
\item Retrieve the kadmin/history key \k{h} and decrypt it with \k{M}.
\item Encrypt the principal's new key in \k{h}.
\item If the principal's new key encrypted in \k{h} is in old_keys,
return KADM5_PASS_REUSE.
\item Encrypt the principal's current key in \k{h} and store it in
old_keys.
\item Erase the memory containing \k{h}.
\end{enumerate}

\item Encrypt the principal's new key in \k{M} and store it in the
database.
\item Erase the memory containing \k{M}.
\end{enumerate}

To store the an encrypted key in old_keys, insert it as the
old_key_next element of old_keys, and increment old_key_next by one
modulo pw_history_num.

\subsection{kadm5_get_principal}

If the named principal exists in either the Kerberos or admin
principal database, but not both, return KADM5_BAD_DB.

\end{document}