diff options
-rw-r--r-- | src/lib/kadm5/srv/ChangeLog | 5 | ||||
-rw-r--r-- | src/lib/kadm5/srv/Makefile.in | 92 | ||||
-rw-r--r-- | src/lib/kadm5/srv/adb_free.c | 75 | ||||
-rw-r--r-- | src/lib/kadm5/srv/adb_openclose.c | 338 | ||||
-rw-r--r-- | src/lib/kadm5/srv/adb_policy.c | 401 | ||||
-rw-r--r-- | src/lib/kadm5/srv/adb_principal.c | 412 | ||||
-rw-r--r-- | src/lib/kadm5/srv/adb_xdr.c | 132 | ||||
-rw-r--r-- | src/lib/kadm5/srv/configure.in | 42 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_acl.c | 511 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_acl.h | 81 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_dict.c | 199 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_handle.c | 9 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_init.c | 330 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_kdb.c | 424 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_misc.c | 101 | ||||
-rw-r--r-- | src/lib/kadm5/srv/svr_chpass_util.c | 15 | ||||
-rw-r--r-- | src/lib/kadm5/srv/svr_iters.c | 248 | ||||
-rw-r--r-- | src/lib/kadm5/srv/svr_misc_free.c | 37 | ||||
-rw-r--r-- | src/lib/kadm5/srv/svr_policy.c | 315 | ||||
-rw-r--r-- | src/lib/kadm5/srv/svr_principal.c | 1350 |
20 files changed, 5117 insertions, 0 deletions
diff --git a/src/lib/kadm5/srv/ChangeLog b/src/lib/kadm5/srv/ChangeLog new file mode 100644 index 0000000..f91603f --- /dev/null +++ b/src/lib/kadm5/srv/ChangeLog @@ -0,0 +1,5 @@ +Wed Jul 24 18:21:28 1996 Tom Yu <tlyu@voltage-multiplier.mit.edu> + + * Makefile.in, configure.in: break out server lib into a + subdirectory + diff --git a/src/lib/kadm5/srv/Makefile.in b/src/lib/kadm5/srv/Makefile.in new file mode 100644 index 0000000..4308ed4 --- /dev/null +++ b/src/lib/kadm5/srv/Makefile.in @@ -0,0 +1,92 @@ +CFLAGS = $(CCOPTS) $(DEFS) -I$(BUILDTOP)/include/kadm5 + +##DOSBUILDTOP = ..\..\.. +##DOSLIBNAME = libkadm5srv.lib + +.c.o: + $(CC) $(CFLAGS) -c $(srcdir)/$*.c +@SHARED_RULE@ + +SRCS = $(srcdir)/svr_policy.c \ + $(srcdir)/svr_principal.c \ + $(srcdir)/server_acl.c \ + $(srcdir)/server_kdb.c \ + $(srcdir)/server_misc.c \ + $(srcdir)/server_init.c \ + $(srcdir)/server_dict.c \ + $(srcdir)/svr_iters.c \ + $(srcdir)/svr_chpass_util.c \ + $(srcdir)/adb_xdr.c \ + $(srcdir)/adb_policy.c \ + $(srcdir)/adb_free.c \ + $(srcdir)/adb_openclose.c + +OBJS = svr_policy.$(OBJEXT) \ + svr_principal.$(OBJEXT) \ + server_acl.$(OBJEXT) \ + server_kdb.$(OBJEXT) \ + server_misc.$(OBJEXT) \ + server_init.$(OBJEXT) \ + server_dict.$(OBJEXT) \ + svr_iters.$(OBJEXT) \ + svr_chpass_util.$(OBJEXT) \ + adb_xdr.$(OBJEXT) \ + adb_policy.$(OBJEXT) \ + adb_free.$(OBJEXT) \ + adb_openclose.$(OBJEXT) + +LIBUPDATE=$(BUILDTOP)/util/libupdate + +# +# Depends on libgssrpc, libgssapi_krb5, libkdb5, libkrb5, libcrypto, +# libcom_err, libdyn +# +GSSRPC_VER=@GSSRPC_SH_VERS@ +GSSAPI_KRB5_VER=@GSSAPI_KRB5_SH_VERS@ +KDB5_VER=@KDB5_SH_VERS@ +KRB5_VER=@KRB5_SH_VERS@ +CRYPTO_VER=@CRYPTO_SH_VERS@ +COMERR_VER=@COMERR_SH_VERS@ +DYN_VER=@DYN_SH_VERS@ +DEPLIBS=$(TOPLIBD)/libgssrpc.$(SHEXT).$(GSSRPC_VER) \ + $(TOPLIBD)/libgssapi_krb5.$(SHEXT).$(GSSAPI_KRB5_VER) \ + $(TOPLIBD)/libkdb5.$(SHEXT).$(KDB5_VER) \ + $(TOPLIBD)/libkrb5.$(SHEXT).$(KRB5_VER) \ + $(TOPLIBD)/libcrypto.$(SHEXT).$(CRYPTO_VER) \ + $(TOPLIBD)/libcom_err.$(SHEXT).$(COMERR_VER) \ + $(TOPLIBD)/libdyn.$(SHEXT).$(DYN_VER) + +SHLIB_LIBS=-lgssrpc -lgssapi_krb5 -lkdb5 -ldb -lkrb5 -lcrypto -lcom_err -ldyn +SHLIB_LDFLAGS= $(LDFLAGS) @SHLIB_RPATH_DIRS@ +SHLIB_LIBDIRS= @SHLIB_LIBDIRS@ + +all-unix:: shared includes $(OBJS) +all-mac:: $(OBJS) +all-windows:: $(OBJS) + +LIBDONE=../DONE DONE +LIB_SUBDIRS=.. . + +shared: + mkdir shared + +libkadm5srv.$(STEXT): $(LIBDONE) + @if test -f $@ ; then \ + (set -x; $(LIBUPDATE) $@ DONE $(LIB_SUBDIRS)) \ + else \ + (set -x; $(LIBUPDATE) --force $@ DONE $(LIB_SUBDIRS)) \ + fi + $(RANLIB) $@ + touch libkadm5srv.stamp + +check-windows:: + +clean-mac:: +clean-windows:: + +clean-unix:: + $(RM) libkadm5srv.$(STEXT) libkadm5srv.stamp + +install:: libkadm5srv.a + $(INSTALL_DATA) libkadm5srv.a $(DESTDIR)$(KRB5_LIBDIR)/libkadm5srv.a + $(RANLIB) $(DESTDIR)$(KRB5_LIBDIR)/libkadm5srv.a diff --git a/src/lib/kadm5/srv/adb_free.c b/src/lib/kadm5/srv/adb_free.c new file mode 100644 index 0000000..a58af17 --- /dev/null +++ b/src/lib/kadm5/srv/adb_free.c @@ -0,0 +1,75 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + * + * $Log$ + * Revision 1.1 1996/07/24 22:23:09 tlyu + * * Makefile.in, configure.in: break out server lib into a + * subdirectory + * + * Revision 1.8 1996/07/22 20:35:16 marc + * this commit includes all the changes on the OV_9510_INTEGRATION and + * OV_MERGE branches. This includes, but is not limited to, the new openvision + * admin system, and major changes to gssapi to add functionality, and bring + * the implementation in line with rfc1964. before committing, the + * code was built and tested for netbsd and solaris. + * + * Revision 1.7.4.1 1996/07/18 03:08:07 marc + * merged in changes from OV_9510_BP to OV_9510_FINAL1 + * + * Revision 1.7.2.1 1996/06/20 02:16:25 marc + * File added to the repository on a branch + * + * Revision 1.7 1996/05/12 06:21:57 marc + * don't use <absolute paths> for "internal header files" + * + * Revision 1.6 1993/12/13 21:15:56 shanzer + * fixed memory leak + * ., + * + * Revision 1.5 1993/12/06 22:20:37 marc + * fixup free functions to use xdr to free the underlying struct + * + * Revision 1.4 1993/11/15 00:29:46 shanzer + * check to make sure pointers are somewhat vaid before freeing. + * + * Revision 1.3 1993/11/09 04:02:24 shanzer + * added some includefiles + * changed bzero to memset + * + * Revision 1.2 1993/11/04 01:54:24 shanzer + * added rcs header .. + * + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include "adb.h" +#include <memory.h> +#include <malloc.h> + +void +osa_free_princ_ent(osa_princ_ent_t val) +{ + XDR xdrs; + + xdrmem_create(&xdrs, NULL, 0, XDR_FREE); + + xdr_osa_princ_ent_rec(&xdrs, val); + free(val); +} + +void +osa_free_policy_ent(osa_policy_ent_t val) +{ + XDR xdrs; + + xdrmem_create(&xdrs, NULL, 0, XDR_FREE); + + xdr_osa_policy_ent_rec(&xdrs, val); + free(val); +} + diff --git a/src/lib/kadm5/srv/adb_openclose.c b/src/lib/kadm5/srv/adb_openclose.c new file mode 100644 index 0000000..627a6b4 --- /dev/null +++ b/src/lib/kadm5/srv/adb_openclose.c @@ -0,0 +1,338 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include <sys/file.h> +#include <fcntl.h> +#include <unistd.h> +#include "adb.h" +#include <stdlib.h> + +#define MAX_LOCK_TRIES 5 + +struct _locklist { + osa_adb_lock_ent lockinfo; + struct _locklist *next; +}; + +osa_adb_ret_t osa_adb_create_db(char *filename, char *lockfilename, + int magic) +{ + FILE *lf; + DB *db; + HASHINFO info; + + lf = fopen(lockfilename, "w+"); + if (lf == NULL) + return errno; + (void) fclose(lf); + + memset(&info, 0, sizeof(info)); + info.hash = NULL; + info.bsize = 256; + info.ffactor = 8; + info.nelem = 25000; + info.lorder = 0; + db = dbopen(filename, O_RDWR | O_CREAT | O_EXCL, 0600, DB_HASH, &info); + if (db == NULL) + return errno; + if (db->close(db) < 0) + return errno; + return OSA_ADB_OK; +} + +osa_adb_ret_t osa_adb_destroy_db(char *filename, char *lockfilename, + int magic) +{ + /* the admin databases do not contain security-critical data */ + if (unlink(filename) < 0 || + unlink(lockfilename) < 0) + return errno; + return OSA_ADB_OK; +} + +osa_adb_ret_t osa_adb_init_db(osa_adb_db_t *dbp, char *filename, + char *lockfilename, int magic) +{ + osa_adb_db_t db; + static struct _locklist *locklist = NULL; + struct _locklist *lockp; + krb5_error_code code; + + if (dbp == NULL || filename == NULL) + return EINVAL; + + db = (osa_adb_princ_t) malloc(sizeof(osa_adb_db_ent)); + if (db == NULL) + return ENOMEM; + + memset(db, 0, sizeof(*db)); + db->info.hash = NULL; + db->info.bsize = 256; + db->info.ffactor = 8; + db->info.nelem = 25000; + db->info.lorder = 0; + + /* + * A process is allowed to open the same database multiple times + * and access it via different handles. If the handles use + * distinct lockinfo structures, things get confused: lock(A), + * lock(B), release(B) will result in the kernel unlocking the + * lock file but handle A will still think the file is locked. + * Therefore, all handles using the same lock file must share a + * single lockinfo structure. + * + * It is not sufficient to have a single lockinfo structure, + * however, because a single process may also wish to open + * multiple different databases simultaneously, with different + * lock files. This code used to use a single static lockinfo + * structure, which means that the second database opened used + * the first database's lock file. This was Bad. + * + * We now maintain a linked list of lockinfo structures, keyed by + * lockfilename. An entry is added when this function is called + * with a new lockfilename, and all subsequent calls with that + * lockfilename use the existing entry, updating the refcnt. + * When the database is closed with fini_db(), the refcnt is + * decremented, and when it is zero the lockinfo structure is + * freed and reset. The entry in the linked list, however, is + * never removed; it will just be reinitialized the next time + * init_db is called with the right lockfilename. + */ + + /* find or create the lockinfo structure for lockfilename */ + lockp = locklist; + while (lockp) { + if (strcmp(lockp->lockinfo.filename, lockfilename) == 0) + break; + else + lockp = lockp->next; + } + if (lockp == NULL) { + /* doesn't exist, create it, add to list */ + lockp = (struct _locklist *) malloc(sizeof(*lockp)); + if (lockp == NULL) { + free(db); + return ENOMEM; + } + memset(lockp, 0, sizeof(*lockp)); + lockp->next = locklist; + locklist = lockp; + } + + /* now initialize lockp->lockinfo if necessary */ + if (lockp->lockinfo.lockfile == NULL) { + if (code = krb5_init_context(&lockp->lockinfo.context)) { + free(db); + return((osa_adb_ret_t) code); + } + + /* + * needs be open read/write so that write locking can work with + * POSIX systems + */ + lockp->lockinfo.filename = strdup(lockfilename); + if ((lockp->lockinfo.lockfile = fopen(lockfilename, "r+")) == NULL) { + /* + * maybe someone took away write permission so we could only + * get shared locks? + */ + if ((lockp->lockinfo.lockfile = fopen(lockfilename, "r")) + == NULL) { + free(db); + return OSA_ADB_NOLOCKFILE; + } + } + lockp->lockinfo.lockmode = lockp->lockinfo.lockcnt = 0; + } + + /* lockp is set, lockinfo is initialized, update the reference count */ + db->lock = &lockp->lockinfo; + db->lock->refcnt++; + + db->filename = strdup(filename); + db->magic = magic; + + *dbp = db; + + return OSA_ADB_OK; +} + +osa_adb_ret_t osa_adb_fini_db(osa_adb_db_t db, int magic) +{ + if (db->magic != magic) + return EINVAL; + if (db->lock->refcnt == 0) { + /* barry says this can't happen */ + return OSA_ADB_FAILURE; + } else { + db->lock->refcnt--; + } + + if (db->lock->refcnt == 0) { + /* + * Don't free db->lock->filename, it is used as a key to + * find the lockinfo entry in the linked list. If the + * lockfile doesn't exist, we must be closing the database + * after trashing it. This has to be allowed, so don't + * generate an error. + */ + (void) fclose(db->lock->lockfile); + db->lock->lockfile = NULL; + krb5_free_context(db->lock->context); + } + + db->magic = 0; + free(db->filename); + free(db); + return OSA_ADB_OK; +} + +osa_adb_ret_t osa_adb_get_lock(osa_adb_db_t db, int mode) +{ + int tries, gotlock, perm, krb5_mode, ret; + + if (db->lock->lockmode >= mode) { + /* No need to upgrade lock, just incr refcnt and return */ + db->lock->lockcnt++; + return(OSA_ADB_OK); + } + + perm = 0; + switch (mode) { + case OSA_ADB_PERMANENT: + perm = 1; + case OSA_ADB_EXCLUSIVE: + krb5_mode = KRB5_LOCKMODE_EXCLUSIVE; + break; + case OSA_ADB_SHARED: + krb5_mode = KRB5_LOCKMODE_SHARED; + break; + default: + return(EINVAL); + } + + for (gotlock = tries = 0; tries < MAX_LOCK_TRIES; tries++) { + if ((ret = krb5_lock_file(db->lock->context, + fileno(db->lock->lockfile), + krb5_mode|KRB5_LOCKMODE_DONTBLOCK)) == 0) { + gotlock++; + break; + } else if (ret == EBADF && mode == OSA_ADB_EXCLUSIVE) + /* tried to exclusive-lock something we don't have */ + /* write access to */ + return OSA_ADB_NOEXCL_PERM; + + sleep(1); + } + + /* test for all the likely "can't get lock" error codes */ + if (ret == EACCES || ret == EAGAIN || ret == EWOULDBLOCK) + return OSA_ADB_CANTLOCK_DB; + else if (ret != 0) + return ret; + + /* + * If the file no longer exists, someone acquired a permanent + * lock. If that process terminates its exclusive lock is lost, + * but if we already had the file open we can (probably) lock it + * even though it has been unlinked. So we need to insist that + * it exist. + */ + if (access(db->lock->filename, F_OK) < 0) { + (void) krb5_lock_file(db->lock->context, + fileno(db->lock->lockfile), + KRB5_LOCKMODE_UNLOCK); + return OSA_ADB_NOLOCKFILE; + } + + /* we have the shared/exclusive lock */ + + if (perm) { + if (unlink(db->lock->filename) < 0) { + int ret; + + /* somehow we can't delete the file, but we already */ + /* have the lock, so release it and return */ + + ret = errno; + (void) krb5_lock_file(db->lock->context, + fileno(db->lock->lockfile), + KRB5_LOCKMODE_UNLOCK); + + /* maybe we should return CANTLOCK_DB.. but that would */ + /* look just like the db was already locked */ + return ret; + } + + /* this releases our exclusive lock.. which is okay because */ + /* now no one else can get one either */ + (void) fclose(db->lock->lockfile); + } + + db->lock->lockmode = mode; + db->lock->lockcnt++; + return OSA_ADB_OK; +} + +osa_adb_ret_t osa_adb_release_lock(osa_adb_db_t db) +{ + int ret; + + if (!db->lock->lockcnt) /* lock already unlocked */ + return OSA_ADB_NOTLOCKED; + + if (--db->lock->lockcnt == 0) { + if (db->lock->lockmode == OSA_ADB_PERMANENT) { + /* now we need to create the file since it does not exist */ + if ((db->lock->lockfile = fopen(db->lock->filename, + "w+")) == NULL) + return OSA_ADB_NOLOCKFILE; + } else if (ret = krb5_lock_file(db->lock->context, + fileno(db->lock->lockfile), + KRB5_LOCKMODE_UNLOCK)) + return ret; + + db->lock->lockmode = 0; + } + return OSA_ADB_OK; +} + +osa_adb_ret_t osa_adb_open_and_lock(osa_adb_princ_t db, int locktype) +{ + int ret; + + ret = osa_adb_get_lock(db, locktype); + if (ret != OSA_ADB_OK) + return ret; + + db->db = dbopen(db->filename, O_RDWR, 0600, DB_HASH, &db->info); + if (db->db == NULL) { + (void) osa_adb_release_lock(db); + if(errno == EINVAL) + return OSA_ADB_BAD_DB; + return errno; + } + return OSA_ADB_OK; +} + +osa_adb_ret_t osa_adb_close_and_unlock(osa_adb_princ_t db) +{ + int ret; + + if(db->db->close(db->db) == -1) { + (void) osa_adb_release_lock(db); + return OSA_ADB_FAILURE; + } + + db->db = NULL; + + return(osa_adb_release_lock(db)); +} + diff --git a/src/lib/kadm5/srv/adb_policy.c b/src/lib/kadm5/srv/adb_policy.c new file mode 100644 index 0000000..ff0117b --- /dev/null +++ b/src/lib/kadm5/srv/adb_policy.c @@ -0,0 +1,401 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include <sys/file.h> +#include <fcntl.h> +#include "adb.h" +#include <stdlib.h> +#include <malloc.h> +#include <string.h> + +extern int errno; + +#define OPENLOCK(db, mode) \ +{ \ + int ret; \ + if (db == NULL) \ + return EINVAL; \ + else if (db->magic != OSA_ADB_POLICY_DB_MAGIC) \ + return OSA_ADB_DBINIT; \ + else if ((ret = osa_adb_open_and_lock(db, mode)) != OSA_ADB_OK) \ + return ret; \ + } + +#define CLOSELOCK(db) \ +{ \ + int ret; \ + if ((ret = osa_adb_close_and_unlock(db)) != OSA_ADB_OK) \ + return ret; \ +} + +osa_adb_ret_t osa_adb_create_policy_db(kadm5_config_params *params) +{ + return osa_adb_create_db(params->admin_dbname, + params->admin_lockfile, + OSA_ADB_POLICY_DB_MAGIC); +} + +osa_adb_ret_t osa_adb_destroy_policy_db(kadm5_config_params *params) +{ + return osa_adb_destroy_db(params->admin_dbname, + params->admin_lockfile, + OSA_ADB_POLICY_DB_MAGIC); +} + +osa_adb_ret_t osa_adb_open_policy(osa_adb_princ_t *dbp, + kadm5_config_params *rparams) +{ + return osa_adb_init_db(dbp, rparams->admin_dbname, + rparams->admin_lockfile, + OSA_ADB_POLICY_DB_MAGIC); +} + +osa_adb_ret_t osa_adb_close_policy(osa_adb_princ_t db) +{ + return osa_adb_fini_db(db, OSA_ADB_POLICY_DB_MAGIC); +} + +/* + * Function: osa_adb_create_policy + * + * Purpose: create a policy entry in the policy db. + * + * Arguments: + * entry (input) pointer to the entry to be added + * <return value> OSA_ADB_OK on sucsess, else error code. + * + * Requires: + * entry have a valid name. + * + * Effects: + * creates the entry in the db + * + * Modifies: + * the policy db. + * + */ +osa_adb_ret_t +osa_adb_create_policy(osa_adb_policy_t db, osa_policy_ent_t entry) +{ + DBT dbkey; + DBT dbdata; + XDR xdrs; + int ret; + + OPENLOCK(db, OSA_ADB_EXCLUSIVE); + + if(entry->name == NULL) { + ret = EINVAL; + goto error; + } + dbkey.data = entry->name; + dbkey.size = (strlen(entry->name) + 1); + + switch(db->db->get(db->db, &dbkey, &dbdata, 0)) { + case 0: + ret = OSA_ADB_DUP; + goto error; + case 1: + break; + default: + ret = errno; + goto error; + } + xdralloc_create(&xdrs, XDR_ENCODE); + if(!xdr_osa_policy_ent_rec(&xdrs, entry)) { + xdr_destroy(&xdrs); + ret = OSA_ADB_XDR_FAILURE; + goto error; + } + dbdata.data = xdralloc_getdata(&xdrs); + dbdata.size = xdr_getpos(&xdrs); + switch(db->db->put(db->db, &dbkey, &dbdata, R_NOOVERWRITE)) { + case 0: + if((db->db->sync(db->db, 0)) == -1) + ret = OSA_ADB_FAILURE; + ret = OSA_ADB_OK; + break; + case 1: + ret = OSA_ADB_DUP; + break; + default: + ret = OSA_ADB_FAILURE; + break; + } + xdr_destroy(&xdrs); + +error: + CLOSELOCK(db); + return ret; +} + +/* + * Function: osa_adb_destroy_policy + * + * Purpose: destroy a policy entry + * + * Arguments: + * db (input) database handle + * name (input) name of policy + * <return value> OSA_ADB_OK on sucsess, or error code. + * + * Requires: + * db being valid. + * name being non-null. + * Effects: + * deletes policy from db. + * + * Modifies: + * policy db. + * + */ +osa_adb_ret_t +osa_adb_destroy_policy(osa_adb_policy_t db, kadm5_policy_t name) +{ + DBT dbkey; + int status, ret; + + OPENLOCK(db, OSA_ADB_EXCLUSIVE); + + if(name == NULL) { + ret = EINVAL; + goto error; + } + dbkey.data = name; + dbkey.size = (strlen(name) + 1); + + status = db->db->del(db->db, &dbkey, 0); + switch(status) { + case 1: + ret = OSA_ADB_NOENT; + goto error; + case 0: + if ((db->db->sync(db->db, 0)) == -1) { + ret = OSA_ADB_FAILURE; + goto error; + } + ret = OSA_ADB_OK; + break; + default: + ret = OSA_ADB_FAILURE; + goto error; + } + +error: + CLOSELOCK(db); + return ret; +} + +/* + * Function: osa_adb_get_policy + * + * Purpose: retrieve policy + * + * Arguments: + * db (input) db handle + * name (input) name of policy + * entry (output) policy entry + * <return value> 0 on sucsess, error code on failure. + * + * Requires: + * Effects: + * Modifies: + */ +osa_adb_ret_t +osa_adb_get_policy(osa_adb_policy_t db, kadm5_policy_t name, + osa_policy_ent_t *entry) +{ + DBT dbkey; + DBT dbdata; + XDR xdrs; + int ret; + char *aligned_data; + + OPENLOCK(db, OSA_ADB_SHARED); + + if(name == NULL) { + ret = EINVAL; + goto error; + } + dbkey.data = name; + dbkey.size = (strlen(dbkey.data) + 1); + dbdata.data = NULL; + dbdata.size = 0; + switch((db->db->get(db->db, &dbkey, &dbdata, 0))) { + case 1: + ret = OSA_ADB_NOENT; + goto error; + case 0: + break; + default: + ret = OSA_ADB_FAILURE; + goto error; + } + if (!(*(entry) = (osa_policy_ent_t)malloc(sizeof(osa_policy_ent_rec)))) { + ret = ENOMEM; + goto error; + } + if (!(aligned_data = (char *) malloc(dbdata.size))) { + ret = ENOMEM; + goto error; + } + memcpy(aligned_data, dbdata.data, dbdata.size); + memset(*entry, 0, sizeof(osa_policy_ent_rec)); + xdrmem_create(&xdrs, aligned_data, dbdata.size, XDR_DECODE); + if (!xdr_osa_policy_ent_rec(&xdrs, *entry)) + ret = OSA_ADB_FAILURE; + else ret = OSA_ADB_OK; + xdr_destroy(&xdrs); + free(aligned_data); + +error: + CLOSELOCK(db); + return ret; +} + +/* + * Function: osa_adb_put_policy + * + * Purpose: update a policy in the dababase + * + * Arguments: + * db (input) db handle + * entry (input) policy entry + * <return value> 0 on sucsess error code on failure. + * + * Requires: + * [requires] + * + * Effects: + * [effects] + * + * Modifies: + * [modifies] + * + */ +osa_adb_ret_t +osa_adb_put_policy(osa_adb_policy_t db, osa_policy_ent_t entry) +{ + DBT dbkey; + DBT dbdata; + DBT tmpdb; + XDR xdrs; + int ret; + + OPENLOCK(db, OSA_ADB_EXCLUSIVE); + + if(entry->name == NULL) { + ret = EINVAL; + goto error; + } + dbkey.data = entry->name; + dbkey.size = (strlen(entry->name) + 1); + switch(db->db->get(db->db, &dbkey, &tmpdb, 0)) { + case 0: + break; + case 1: + ret = OSA_ADB_NOENT; + goto error; + default: + ret = OSA_ADB_FAILURE; + goto error; + } + xdralloc_create(&xdrs, XDR_ENCODE); + if(!xdr_osa_policy_ent_rec(&xdrs, entry)) { + xdr_destroy(&xdrs); + ret = OSA_ADB_XDR_FAILURE; + goto error; + } + dbdata.data = xdralloc_getdata(&xdrs); + dbdata.size = xdr_getpos(&xdrs); + switch(db->db->put(db->db, &dbkey, &dbdata, 0)) { + case 0: + if((db->db->sync(db->db, 0)) == -1) + ret = OSA_ADB_FAILURE; + ret = OSA_ADB_OK; + break; + default: + ret = OSA_ADB_FAILURE; + break; + } + xdr_destroy(&xdrs); + +error: + CLOSELOCK(db); + return ret; +} + +/* + * Function: osa_adb_iter_policy + * + * Purpose: iterate over the policy database. + * + * Arguments: + * db (input) db handle + * func (input) fucntion pointer to call + * data opaque data type + * <return value> 0 on sucsess error code on failure + * + * Requires: + * Effects: + * Modifies: + */ +osa_adb_ret_t +osa_adb_iter_policy(osa_adb_policy_t db, osa_adb_iter_policy_func func, + void *data) +{ + DBT dbkey, + dbdata; + XDR xdrs; + int ret; + osa_policy_ent_t entry; + char *aligned_data; + + OPENLOCK(db, OSA_ADB_EXCLUSIVE); /* hmmm */ + + if((ret = db->db->seq(db->db, &dbkey, &dbdata, R_FIRST)) == -1) { + ret = errno; + goto error; + } + + while (ret == 0) { + if (!(entry = (osa_policy_ent_t) malloc(sizeof(osa_policy_ent_rec)))) { + ret = ENOMEM; + goto error; + } + + if(!(aligned_data = (char *) malloc(dbdata.size))) { + ret = ENOMEM; + goto error; + } + memcpy(aligned_data, dbdata.data, dbdata.size); + + memset(entry, 0, sizeof(osa_policy_ent_rec)); + xdrmem_create(&xdrs, aligned_data, dbdata.size, XDR_DECODE); + if(!xdr_osa_policy_ent_rec(&xdrs, entry)) { + xdr_destroy(&xdrs); + free(aligned_data); + ret = OSA_ADB_FAILURE; + goto error; + } + (*func)(data, entry); + xdr_destroy(&xdrs); + free(aligned_data); + osa_free_policy_ent(entry); + ret = db->db->seq(db->db, &dbkey, &dbdata, R_NEXT); + } + if(ret == -1) + ret = errno; + else ret = OSA_ADB_OK; + +error: + CLOSELOCK(db); + return ret; +} diff --git a/src/lib/kadm5/srv/adb_principal.c b/src/lib/kadm5/srv/adb_principal.c new file mode 100644 index 0000000..8c2e9e2 --- /dev/null +++ b/src/lib/kadm5/srv/adb_principal.c @@ -0,0 +1,412 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + * + * $Log$ + * Revision 1.1 1996/07/24 22:23:12 tlyu + * * Makefile.in, configure.in: break out server lib into a + * subdirectory + * + * Revision 1.24 1996/07/22 20:35:23 marc + * this commit includes all the changes on the OV_9510_INTEGRATION and + * OV_MERGE branches. This includes, but is not limited to, the new openvision + * admin system, and major changes to gssapi to add functionality, and bring + * the implementation in line with rfc1964. before committing, the + * code was built and tested for netbsd and solaris. + * + * Revision 1.23.4.1 1996/07/18 03:08:17 marc + * merged in changes from OV_9510_BP to OV_9510_FINAL1 + * + * Revision 1.23.2.1 1996/06/20 02:16:30 marc + * File added to the repository on a branch + * + * Revision 1.23 1996/05/16 21:44:35 bjaspan + * this file is no longer used, #if the whole thing out + * + * Revision 1.22 1996/05/08 20:51:44 bjaspan + * marc's changes + * + * Revision 1.21 1995/08/24 20:23:43 bjaspan + * marc is a bonehead + * + * Revision 1.20 1995/08/23 19:16:02 marc + * check for db == NULL in OPENLOCK() + * + * Revision 1.19 1995/08/08 18:31:30 bjaspan + * [secure/3394] first cut at admin db locking support + * + * Revision 1.18 1995/08/02 15:26:57 bjaspan + * check db==NULL in iter + * + * Revision 1.17 1994/05/09 17:52:36 shanzer + * fixed some include files + * + * Revision 1.16 1994/03/17 01:25:58 shanzer + * include fcntl.h + * + * Revision 1.15 1993/12/17 18:54:06 jik + * [secure-admin/1040] + * + * open_princ should return errno, rather than BAD_DB, if errno is + * something other than BAD_DB. + * + * Revision 1.14 1993/12/13 18:55:58 marc + * remove bogus free()'s + * + * Revision 1.13 1993/12/08 22:29:27 marc + * fixed another xdrmem alignment thing] + * + * Revision 1.12 1993/12/06 22:22:22 bjaspan + * fix alignment and free-memory-read bugs + * + * Revision 1.11 1993/12/05 04:15:16 shanzer + * removed data size hack. + * + * Revision 1.10 1993/11/15 00:29:24 shanzer + * added filenme to open + * + * Revision 1.9 1993/11/10 20:10:06 shanzer + * now uses xdralloc instead of xdrmem + * + * Revision 1.8 1993/11/09 21:43:24 shanzer + * added check to see if we overflowed our xdr buffer. + * + * Revision 1.7 1993/11/09 04:00:19 shanzer + * changed bzero to memset + * + * Revision 1.6 1993/11/05 23:16:21 shanzer + * return ENOMEM instead of ovsec_kadm_mem + * + * Revision 1.5 1993/11/05 22:17:03 shanzer + * added principal db interative function + * + * Revision 1.4 1993/11/04 23:20:24 shanzer + * made HASHINFO static. + * + * Revision 1.3 1993/11/04 01:52:30 shanzer + * Restructred some code .. fixed some bugs/leaks + * + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#if 0 +/* XXX THIS FILE IS NO LONGER USED, and should be deleted when we're done */ + +#include <sys/file.h> +#include <fcntl.h> +#include "adb.h" +#include <stdlib.h> +#include <memory.h> + +#define OPENLOCK(db, mode) \ +{ \ + int ret; \ + if (db == NULL) \ + return EINVAL; \ + else if (db->magic != OSA_ADB_PRINC_DB_MAGIC) \ + return OSA_ADB_DBINIT; \ + else if ((ret = osa_adb_open_and_lock(db, mode)) != OSA_ADB_OK) \ + return ret; \ + } + +#define CLOSELOCK(db) \ +{ \ + int ret; \ + if ((ret = osa_adb_close_and_unlock(db)) != OSA_ADB_OK) \ + return ret; \ +} + +osa_adb_ret_t osa_adb_open_princ(osa_adb_princ_t *dbp, char *filename) +{ + return osa_adb_init_db(dbp, filename, OSA_ADB_PRINC_DB_MAGIC); +} + +osa_adb_ret_t osa_adb_close_princ(osa_adb_princ_t db) +{ + return osa_adb_fini_db(db, OSA_ADB_PRINC_DB_MAGIC); +} + +osa_adb_ret_t +osa_adb_create_princ(osa_adb_princ_t db, osa_princ_ent_t entry) +{ + + DBT dbkey; + DBT dbdata; + XDR xdrs; + int ret; + + OPENLOCK(db, OSA_ADB_EXCLUSIVE); + + if(krb5_unparse_name(db->lock->context, + entry->name, (char **) &dbkey.data)) { + ret = OSA_ADB_BAD_PRINC; + goto error; + } + if((dbkey.size = strlen(dbkey.data)) == 0) { + ret = OSA_ADB_BAD_PRINC; + goto error; + } + + switch(db->db->get(db->db, &dbkey, &dbdata, 0)) { + case 0: + ret = OSA_ADB_DUP; + goto error; + case 1: + break; + default: + ret = OSA_ADB_FAILURE; + goto error; + } + xdralloc_create(&xdrs, XDR_ENCODE); + if(!xdr_osa_princ_ent_rec(&xdrs, entry)) { + xdr_destroy(&xdrs); + ret = OSA_ADB_XDR_FAILURE; + goto error; + } + dbdata.data = xdralloc_getdata(&xdrs); + dbdata.size = xdr_getpos(&xdrs); + switch(db->db->put(db->db, &dbkey, &dbdata, R_NOOVERWRITE)) { + case 0: + if((db->db->sync(db->db, 0)) == -1) + ret = OSA_ADB_FAILURE; + else + ret = OSA_ADB_OK; + break; + case 1: + ret = OSA_ADB_DUP; + break; + default: + ret = OSA_ADB_FAILURE; + break; + } + xdralloc_release(&xdrs); + free(dbkey.data); + +error: + CLOSELOCK(db); + + return ret; +} + +osa_adb_ret_t +osa_adb_destroy_princ(osa_adb_princ_t db, ovsec_kadm_princ_t name) +{ + DBT dbkey; + int status; + int ret; + + OPENLOCK(db, OSA_ADB_EXCLUSIVE); + + if(krb5_unparse_name(db->lock->context, name, (char **) &dbkey.data)) { + ret = OSA_ADB_BAD_PRINC; + goto error; + } + if ((dbkey.size = strlen(dbkey.data)) == 0) { + ret = OSA_ADB_BAD_PRINC; + goto error; + } + status = db->db->del(db->db, &dbkey, 0); + switch(status) { + case 1: + ret = OSA_ADB_NOENT; + break; + case 0: + if ((db->db->sync(db->db, 0)) == -1) + ret = OSA_ADB_FAILURE; + else + ret = OSA_ADB_OK; + break; + default: + ret = OSA_ADB_FAILURE; + break; + } + free(dbkey.data); + +error: + CLOSELOCK(db); + + return ret; +} + +osa_adb_ret_t +osa_adb_get_princ(osa_adb_princ_t db, ovsec_kadm_princ_t name, + osa_princ_ent_t *entry) +{ + DBT dbkey; + DBT dbdata; + XDR xdrs; + int ret = 0; + char *aligned_data; + + OPENLOCK(db, OSA_ADB_SHARED); + + if(krb5_unparse_name(db->lock->context, name, (char **) &dbkey.data)) { + ret = OSA_ADB_BAD_PRINC; + goto error; + } + if((dbkey.size = strlen(dbkey.data)) == 0) { + ret = OSA_ADB_BAD_PRINC; + goto error; + } + dbdata.size = 0; + dbdata.data = NULL; + switch(db->db->get(db->db, &dbkey, &dbdata, 0)) { + case 1: + ret = OSA_ADB_NOENT; + break; + case 0: + break; + default: + ret = OSA_ADB_FAILURE; + break; + } + free(dbkey.data); + if (ret) + goto error; + + if (!(*(entry) = (osa_princ_ent_t)malloc(sizeof(osa_princ_ent_rec)))) { + ret = ENOMEM; + goto error; + } + + aligned_data = (char *) malloc(dbdata.size); + if (aligned_data == NULL) { + ret = ENOMEM; + goto error; + } + memcpy(aligned_data, dbdata.data, dbdata.size); + + memset(*entry, 0, sizeof(osa_princ_ent_rec)); + xdrmem_create(&xdrs, aligned_data, dbdata.size, XDR_DECODE); + if (!xdr_osa_princ_ent_rec(&xdrs, *entry)) { + xdr_destroy(&xdrs); + free(aligned_data); + ret = OSA_ADB_FAILURE; + goto error; + } + xdr_destroy(&xdrs); + free(aligned_data); + ret = OSA_ADB_OK; + +error: + CLOSELOCK(db); + return ret; +} + +osa_adb_ret_t +osa_adb_put_princ(osa_adb_princ_t db, osa_princ_ent_t entry) +{ + DBT dbkey; + DBT dbdata; + DBT tmpdb; + XDR xdrs; + int ret; + + OPENLOCK(db, OSA_ADB_EXCLUSIVE); + + if(krb5_unparse_name(db->lock->context, + entry->name, (char **) &dbkey.data)) { + ret = OSA_ADB_BAD_PRINC; + goto error; + } + if((dbkey.size = strlen(dbkey.data)) == 0) { + ret = OSA_ADB_BAD_PRINC; + goto error; + } + + switch(db->db->get(db->db, &dbkey, &tmpdb, 0)) { + case 0: + break; + case 1: + ret = OSA_ADB_NOENT; + goto error; + default: + ret = OSA_ADB_FAILURE; + goto error; + } + xdralloc_create(&xdrs, XDR_ENCODE); + if(!xdr_osa_princ_ent_rec(&xdrs, entry)) { + xdr_destroy(&xdrs); + ret = OSA_ADB_XDR_FAILURE; + goto error; + } + dbdata.data = xdralloc_getdata(&xdrs); + dbdata.size = xdr_getpos(&xdrs); + switch(db->db->put(db->db, &dbkey, &dbdata, 0)) { + case 0: + if((db->db->sync(db->db, 0)) == -1) + ret = OSA_ADB_FAILURE; + else + ret = OSA_ADB_OK; + break; + default: + ret = OSA_ADB_FAILURE; + break; + } + xdralloc_release(&xdrs); + free(dbkey.data); + +error: + CLOSELOCK(db); + return ret; +} + +osa_adb_ret_t +osa_adb_iter_princ(osa_adb_princ_t db, osa_adb_iter_princ_func func, + void *data) +{ + DBT dbkey, + dbdata; + XDR xdrs; + int ret; + osa_princ_ent_t entry; + char *aligned_data; + + OPENLOCK(db, OSA_ADB_EXCLUSIVE); /* hmmmm */ + + if((ret = db->db->seq(db->db, &dbkey, &dbdata, R_FIRST)) == -1) { + ret = errno; + goto error; + } + while (ret == 0) { + if (!(entry = (osa_princ_ent_t) malloc(sizeof(osa_princ_ent_rec)))) { + ret = ENOMEM; + goto error; + } + + aligned_data = (char *) malloc(dbdata.size); + if (aligned_data == NULL) { + ret = ENOMEM; + goto error; + } + memcpy(aligned_data, dbdata.data, dbdata.size); + + memset(entry, 0, sizeof(osa_princ_ent_rec)); + xdrmem_create(&xdrs, aligned_data, dbdata.size, XDR_DECODE); + if(!xdr_osa_princ_ent_rec(&xdrs, entry)) { + xdr_destroy(&xdrs); + free(aligned_data); + ret = OSA_ADB_FAILURE; + goto error; + } + (*func)(data, entry); + xdr_destroy(&xdrs); + free(aligned_data); + osa_free_princ_ent(entry); + ret = db->db->seq(db->db, &dbkey, &dbdata, R_NEXT); + } + if(ret == -1) + ret = errno; + else + ret = OSA_ADB_OK; + +error: + CLOSELOCK(db); + return ret; +} + +#endif /* 0 */ diff --git a/src/lib/kadm5/srv/adb_xdr.c b/src/lib/kadm5/srv/adb_xdr.c new file mode 100644 index 0000000..944fb04 --- /dev/null +++ b/src/lib/kadm5/srv/adb_xdr.c @@ -0,0 +1,132 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include <sys/types.h> +#include <krb5.h> +#include <rpc/rpc.h> +#include "adb.h" +#include "admin_xdr.h" +#include <memory.h> + +bool_t +xdr_krb5_key_data(XDR *xdrs, krb5_key_data *objp) +{ + unsigned int tmp; + + if (!xdr_krb5_int16(xdrs, &objp->key_data_ver)) + return(FALSE); + if (!xdr_krb5_int16(xdrs, &objp->key_data_kvno)) + return(FALSE); + if (!xdr_krb5_int16(xdrs, &objp->key_data_type[0])) + return(FALSE); + if (!xdr_krb5_int16(xdrs, &objp->key_data_type[1])) + return(FALSE); + if (!xdr_krb5_int16(xdrs, &objp->key_data_length[0])) + return(FALSE); + if (!xdr_krb5_int16(xdrs, &objp->key_data_length[1])) + return(FALSE); + + tmp = (unsigned int) objp->key_data_length[0]; + if (!xdr_bytes(xdrs, (char **) &objp->key_data_contents[0], + &tmp, ~0)) + return FALSE; + + tmp = (unsigned int) objp->key_data_length[1]; + if (!xdr_bytes(xdrs, (char **) &objp->key_data_contents[1], + &tmp, ~0)) + return FALSE; + + /* don't need to copy tmp out, since key_data_length will be set + by the above encoding. */ + + return(TRUE); +} + +bool_t +xdr_osa_pw_hist_ent(XDR *xdrs, osa_pw_hist_ent *objp) +{ + if (!xdr_array(xdrs, (caddr_t *) &objp->key_data, + (u_int *) &objp->n_key_data, ~0, + sizeof(krb5_key_data), + xdr_krb5_key_data)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_osa_princ_ent_rec(XDR *xdrs, osa_princ_ent_t objp) +{ + switch (xdrs->x_op) { + case XDR_ENCODE: + objp->version = OSA_ADB_PRINC_VERSION_1; + /* fall through */ + case XDR_FREE: + if (!xdr_int(xdrs, &objp->version)) + return FALSE; + break; + case XDR_DECODE: + if (!xdr_int(xdrs, &objp->version)) + return FALSE; + if (objp->version != OSA_ADB_PRINC_VERSION_1) + return FALSE; + break; + } + + if (!xdr_nullstring(xdrs, &objp->policy)) + return (FALSE); + if (!xdr_long(xdrs, &objp->aux_attributes)) + return (FALSE); + if (!xdr_u_int(xdrs, &objp->old_key_next)) + return (FALSE); + if (!xdr_krb5_kvno(xdrs, &objp->admin_history_kvno)) + return (FALSE); + if (!xdr_array(xdrs, (caddr_t *) &objp->old_keys, + (unsigned int *) &objp->old_key_len, ~0, + sizeof(osa_pw_hist_ent), + xdr_osa_pw_hist_ent)) + return (FALSE); + return (TRUE); +} + +bool_t +xdr_osa_policy_ent_rec(XDR *xdrs, osa_policy_ent_t objp) +{ + switch (xdrs->x_op) { + case XDR_ENCODE: + objp->version = OSA_ADB_POLICY_VERSION_1; + /* fall through */ + case XDR_FREE: + if (!xdr_int(xdrs, &objp->version)) + return FALSE; + break; + case XDR_DECODE: + if (!xdr_int(xdrs, &objp->version)) + return FALSE; + if (objp->version != OSA_ADB_POLICY_VERSION_1) + return FALSE; + break; + } + + if(!xdr_nullstring(xdrs, &objp->name)) + return (FALSE); + if (!xdr_u_int32(xdrs, &objp->pw_min_life)) + return (FALSE); + if (!xdr_u_int32(xdrs, &objp->pw_max_life)) + return (FALSE); + if (!xdr_u_int32(xdrs, &objp->pw_min_length)) + return (FALSE); + if (!xdr_u_int32(xdrs, &objp->pw_min_classes)) + return (FALSE); + if (!xdr_u_int32(xdrs, &objp->pw_history_num)) + return (FALSE); + if (!xdr_u_int32(xdrs, &objp->policy_refcnt)) + return (FALSE); + return (TRUE); +} diff --git a/src/lib/kadm5/srv/configure.in b/src/lib/kadm5/srv/configure.in new file mode 100644 index 0000000..f17fd5c --- /dev/null +++ b/src/lib/kadm5/srv/configure.in @@ -0,0 +1,42 @@ +AC_INIT(server_kdb.c) +CONFIG_RULES +AC_PROG_ARCHIVE +AC_PROG_ARCHIVE_ADD +AC_PROG_RANLIB +AC_PROG_INSTALL +AC_PROG_LEX +AC_PROG_AWK + +save_LIBS="$LIBS" +LIBS=-lgen +AC_CHECK_FUNCS(compile step) +if test "$ac_cv_func_compile" = true ; then + LIBS="$save_LIBS -lgen" +else + LIBS="$save_LIBS" +fi + +AC_CHECK_FUNCS(re_comp re_exec regcomp regexec) + +V5_SHARED_LIB_OBJS +V5_MAKE_SHARED_LIB(libkadm5srv, 1.0, ../.., ./kadm5/srv) + +GSSRPC_SH_VERS=$krb5_cv_shlib_version_libgssrpc +AC_SUBST(GSSRPC_SH_VERS) +GSSAPI_KRB5_SH_VERS=$krb5_cv_shlib_version_libgssapi_krb5 +AC_SUBST(GSSAPI_KRB5_SH_VERS) +KDB5_SH_VERS=$krb5_cv_shlib_version_libkdb5 +AC_SUBST(KDB5_SH_VERS) +KRB5_SH_VERS=$krb5_cv_shlib_version_libkrb5 +AC_SUBST(KRB5_SH_VERS) +CRYPTO_SH_VERS=$krb5_cv_shlib_version_libcrypto +AC_SUBST(CRYPTO_SH_VERS) +COMERR_SH_VERS=$krb5_cv_shlib_version_libcom_err +AC_SUBST(COMERR_SH_VERS) +DYN_SH_VERS=$krb5_cv_shlib_version_libdyn +AC_SUBST(DYN_SH_VERS) + +SubdirLibraryRule([$(OBJS)]) + +CopySrcHeader(server_acl.h,[$](BUILDTOP)/include/kadm5) +V5_AC_OUTPUT_MAKEFILE diff --git a/src/lib/kadm5/srv/server_acl.c b/src/lib/kadm5/srv/server_acl.c new file mode 100644 index 0000000..16a7f4e --- /dev/null +++ b/src/lib/kadm5/srv/server_acl.c @@ -0,0 +1,511 @@ +/* + * kadmin/v5server/srv_acl.c + * + * Copyright 1995 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + */ + +/* + * srv_acl.c - Handle Kerberos ACL related functions. + */ +#include <stdio.h> +#include <sys/param.h> +#include <gssapi/gssapi_generic.h> +#include "k5-int.h" +#include "server_acl.h" +#include <kadm5/server_internal.h> + +typedef struct _acl_op_table { + char ao_op; + krb5_int32 ao_mask; +} aop_t; + +typedef struct _acl_entry { + struct _acl_entry *ae_next; + char *ae_name; + krb5_boolean ae_name_bad; + krb5_principal ae_principal; + krb5_int32 ae_op_allowed; + char *ae_target; + krb5_boolean ae_target_bad; + krb5_principal ae_target_princ; +} aent_t; + +static const aop_t acl_op_table[] = { + { 'a', ACL_ADD }, + { 'd', ACL_DELETE }, + { 'm', ACL_MODIFY }, + { 'c', ACL_CHANGEPW }, + { 'i', ACL_INQUIRE }, + { 'l', ACL_LIST }, + { 'x', ACL_ALL_MASK }, + { '*', ACL_ALL_MASK }, + { '\0', 0 } +}; + +static aent_t *acl_list_head = (aent_t *) NULL; +static aent_t *acl_list_tail = (aent_t *) NULL; + +static const char *acl_acl_file = (char *) NULL; +static int acl_inited = 0; +static int acl_debug_level = 0; +/* + * This is the catchall entry. If nothing else appropriate is found, or in + * the case where the ACL file is not present, this entry controls what can + * be done. + */ +static const char *acl_catchall_entry = NULL; + +static const char *acl_line2long_msg = "%s: line %d too long, truncated\n"; +static const char *acl_op_bad_msg = "Unrecognized ACL operation '%c' in %s\n"; +static const char *acl_syn_err_msg = "%s: syntax error at line %d <%10s...>\n"; +static const char *acl_cantopen_msg = "\007cannot open ACL file"; + +/* + * acl_get_line() - Get a line from the ACL file. + */ +static char * +acl_get_line(fp, lnp) + FILE *fp; + int *lnp; +{ + int i, domore; + static char acl_buf[BUFSIZ]; + + for (domore = 1; domore && !feof(fp); ) { + /* Copy in the line */ + for (i=0; + ((i<BUFSIZ) && + (!feof(fp)) && + ((acl_buf[i] = fgetc(fp)) != '\n')); + i++); + + /* Check if we exceeded our buffer size */ + if ((i == BUFSIZ) && (!feof(fp)) && (acl_buf[i] != '\n')) { + fprintf(stderr, acl_line2long_msg, acl_acl_file, *lnp); + while (fgetc(fp) != '\n'); + } + acl_buf[i] = '\0'; + if (acl_buf[0] == (char) EOF) /* ptooey */ + acl_buf[0] = '\0'; + else + (*lnp)++; + if ((acl_buf[0] != '#') && (acl_buf[0] != '\0')) + domore = 0; + } + if (domore || (strlen(acl_buf) == 0)) + return((char *) NULL); + else + return(acl_buf); +} + +/* + * acl_parse_line() - Parse the contents of an ACL line. + */ +static aent_t * +acl_parse_line(lp) + char *lp; +{ + static char acle_principal[BUFSIZ]; + static char acle_ops[BUFSIZ]; + static char acle_object[BUFSIZ]; + aent_t *acle; + char *op; + int t, found, opok, nmatch; + + DPRINT(DEBUG_CALLS, acl_debug_level, + ("* acl_parse_line(line=%20s)\n", lp)); + /* + * Format is very simple: + * entry ::= <whitespace> <principal> <whitespace> <opstring> <whitespace> + * [<target> <whitespace>] + */ + acle = (aent_t *) NULL; + acle_object[0] = '\0'; + nmatch = sscanf(lp, "%s %s %s", acle_principal, acle_ops, acle_object); + if (nmatch >= 2) { + acle = (aent_t *) malloc(sizeof(aent_t)); + if (acle) { + acle->ae_next = (aent_t *) NULL; + acle->ae_op_allowed = (krb5_int32) 0; + acle->ae_target = + (nmatch >= 3) ? strdup(acle_object) : (char *) NULL; + acle->ae_target_bad = 0; + acle->ae_target_princ = (krb5_principal) NULL; + opok = 1; + for (op=acle_ops; *op; op++) { + char rop; + + rop = (isupper(*op)) ? tolower(*op) : *op; + found = 0; + for (t=0; acl_op_table[t].ao_op; t++) { + if (rop == acl_op_table[t].ao_op) { + found = 1; + if (rop == *op) + acle->ae_op_allowed |= acl_op_table[t].ao_mask; + else + acle->ae_op_allowed &= ~acl_op_table[t].ao_mask; + } + } + if (!found) { + fprintf(stderr, acl_op_bad_msg, *op, lp); + opok = 0; + } + } + if (opok) { + acle->ae_name = (char *) malloc(strlen(acle_principal)+1); + if (acle->ae_name) { + strcpy(acle->ae_name, acle_principal); + acle->ae_principal = (krb5_principal) NULL; + acle->ae_name_bad = 0; + DPRINT(DEBUG_ACL, acl_debug_level, + ("A ACL entry %s -> opmask %x\n", + acle->ae_name, acle->ae_op_allowed)); + } + else { + if (acle->ae_target) + free(acle->ae_target); + free(acle); + acle = (aent_t *) NULL; + } + } + else { + if (acle->ae_target) + free(acle->ae_target); + free(acle); + acle = (aent_t *) NULL; + } + } + } + DPRINT(DEBUG_CALLS, acl_debug_level, + ("X acl_parse_line() = %x\n", (long) acle)); + return(acle); +} + +/* + * acl_free_entries() - Free all ACL entries. + */ +static void +acl_free_entries() +{ + aent_t *ap; + aent_t *np; + + DPRINT(DEBUG_CALLS, acl_debug_level, ("* acl_free_entries()\n")); + for (ap=acl_list_head; ap; ap = np) { + if (ap->ae_name) + free(ap->ae_name); + if (ap->ae_principal) + krb5_free_principal((krb5_context) NULL, ap->ae_principal); + if (ap->ae_target) + free(ap->ae_target); + if (ap->ae_target_princ) + krb5_free_principal((krb5_context) NULL, ap->ae_target_princ); + np = ap->ae_next; + free(ap); + } + acl_list_head = acl_list_tail = (aent_t *) NULL; + acl_inited = 0; + DPRINT(DEBUG_CALLS, acl_debug_level, ("X acl_free_entries()\n")); +} + +/* + * acl_load_acl_file() - Open and parse the ACL file. + */ +static int +acl_load_acl_file() +{ +char tmpbuf[10]; + FILE *afp; + char *alinep; + aent_t **aentpp; + int alineno; + int retval = 1; + + DPRINT(DEBUG_CALLS, acl_debug_level, ("* acl_load_acl_file()\n")); + /* Open the ACL file for read */ + if (afp = fopen(acl_acl_file, "r")) { + alineno = 1; + aentpp = &acl_list_head; + + /* Get a non-comment line */ + while (alinep = acl_get_line(afp, &alineno)) { + /* Parse it */ + *aentpp = acl_parse_line(alinep); + /* If syntax error, then fall out */ + if (!*aentpp) { + fprintf(stderr, acl_syn_err_msg, + acl_acl_file, alineno, alinep); + retval = 0; + break; + } + acl_list_tail = *aentpp; + aentpp = &(*aentpp)->ae_next; + } + + if (acl_catchall_entry) { + strcpy(tmpbuf, acl_catchall_entry); + if (*aentpp = acl_parse_line(tmpbuf)) { + acl_list_tail = *aentpp; + } + else { + retval = 0; + DPRINT(DEBUG_OPERATION, acl_debug_level, + ("> catchall acl entry (%s) load failed\n", + acl_catchall_entry)); + } + fclose(afp); + } + } + else { + com_err(acl_acl_file, errno, acl_cantopen_msg); + if (acl_list_head = acl_parse_line(acl_catchall_entry)) { + acl_list_tail = acl_list_head; + } + else { + retval = 0; + DPRINT(DEBUG_OPERATION, acl_debug_level, + ("> catchall acl entry (%s) load failed\n", + acl_catchall_entry)); + } + } + + if (!retval) { + acl_free_entries(); + } + DPRINT(DEBUG_CALLS, acl_debug_level, + ("X acl_load_acl_file() = %d\n", retval)); + return(retval); +} + +/* + * acl_match_data() - See if two data entries match. + * + * Wildcarding is only supported for a whole component. + */ +static krb5_boolean +acl_match_data(e1, e2) + krb5_data *e1, *e2; +{ + krb5_boolean retval; + + DPRINT(DEBUG_CALLS, acl_debug_level, + ("* acl_match_entry(%s, %s)\n", e1->data, e2->data)); + retval = 0; + if (!strncmp(e1->data, "*", e1->length) || + !strncmp(e2->data, "*", e2->length)) { + retval = 1; + } + else { + if ((e1->length == e2->length) && + (!strncmp(e1->data, e2->data, e1->length))) + retval = 1; + } + DPRINT(DEBUG_CALLS, acl_debug_level, ("X acl_match_entry()=%d\n",retval)); + return(retval); +} + +/* + * acl_find_entry() - Find a matching entry. + */ +static aent_t * +acl_find_entry(kcontext, principal, dest_princ) + krb5_context kcontext; + krb5_principal principal; + krb5_principal dest_princ; +{ + aent_t *entry; + krb5_error_code kret; + int i; + int matchgood; + + DPRINT(DEBUG_CALLS, acl_debug_level, ("* acl_find_entry()\n")); + for (entry=acl_list_head; entry; entry = entry->ae_next) { + if (!strcmp(entry->ae_name, "*")) { + DPRINT(DEBUG_ACL, acl_debug_level, ("A wildcard ACL match\n")); + break; + } + if (!entry->ae_principal && !entry->ae_name_bad) { + kret = krb5_parse_name(kcontext, + entry->ae_name, + &entry->ae_principal); + if (kret) + entry->ae_name_bad = 1; + } + if (entry->ae_name_bad) { + DPRINT(DEBUG_ACL, acl_debug_level, + ("A Bad ACL entry %s\n", entry->ae_name)); + continue; + } + if (entry->ae_target && + !entry->ae_target_princ && + !entry->ae_target_bad) { + kret = krb5_parse_name(kcontext, + entry->ae_target, + &entry->ae_target_princ); + if (kret) + entry->ae_target_bad = 1; + } + if (entry->ae_target_bad) { + DPRINT(DEBUG_ACL, acl_debug_level, + ("A Bad target in an ACL entry for %s\n", entry->ae_name)); + entry->ae_name_bad = 1; + continue; + } + matchgood = 0; + if (acl_match_data(&entry->ae_principal->realm, + &principal->realm) && + (entry->ae_principal->length == principal->length)) { + matchgood = 1; + for (i=0; i<principal->length; i++) { + if (!acl_match_data(&entry->ae_principal->data[i], + &principal->data[i])) { + matchgood = 0; + break; + } + } + } + if (!matchgood) + continue; + + /* We've matched the principal. If we have a target, then try it */ + if (entry->ae_target && entry->ae_target_princ && dest_princ) { + if (acl_match_data(&entry->ae_target_princ->realm, + &dest_princ->realm) && + (entry->ae_target_princ->length == dest_princ->length)) { + for (i=0; i<dest_princ->length; i++) { + if (!acl_match_data(&entry->ae_target_princ->data[i], + &dest_princ->data[i])) { + matchgood = 0; + break; + } + } + } + else + matchgood = 0; + } + + if (matchgood) + break; + } + DPRINT(DEBUG_CALLS, acl_debug_level, ("X acl_find_entry()=%x\n",entry)); + return(entry); +} + +/* + * acl_init() - Initialize ACL context. + */ +krb5_error_code +acl_init(kcontext, debug_level, acl_file) + krb5_context kcontext; + int debug_level; + char *acl_file; +{ + krb5_error_code kret; + + kret = 0; + acl_debug_level = debug_level; + DPRINT(DEBUG_CALLS, acl_debug_level, + ("* acl_init(afile=%s)\n", + ((acl_file) ? acl_file : "(null)"))); + acl_acl_file = (acl_file) ? acl_file : (char *) KRB5_DEFAULT_ADMIN_ACL; + acl_inited = acl_load_acl_file(); + + DPRINT(DEBUG_CALLS, acl_debug_level, ("X acl_init() = %d\n", kret)); + return(kret); +} + +/* + * acl_finish - Terminate ACL context. + */ +void +acl_finish(kcontext, debug_level) + krb5_context kcontext; + int debug_level; +{ + DPRINT(DEBUG_CALLS, acl_debug_level, ("* acl_finish()\n")); + acl_free_entries(); + DPRINT(DEBUG_CALLS, acl_debug_level, ("X acl_finish()\n")); +} + +/* + * acl_op_permitted() - Is this operation permitted for this principal? + * this code used not to be based on gssapi. In order + * to minimize porting hassles, I've put all the + * gssapi hair in this function. This might not be + * the best medium-term solution. (The best long-term + * solution is, of course, a real authorization service.) + */ +krb5_boolean +acl_check(kcontext, caller, opmask, principal) + krb5_context kcontext; + gss_name_t caller; + krb5_int32 opmask; + krb5_principal principal; +{ + krb5_boolean retval; + aent_t *aentry; + gss_buffer_desc caller_buf; + gss_OID caller_oid; + OM_uint32 emaj, emin; + krb5_error_code code; + krb5_principal caller_princ; + + DPRINT(DEBUG_CALLS, acl_debug_level, ("* acl_op_permitted()\n")); + + if (GSS_ERROR(emaj = gss_display_name(&emin, caller, &caller_buf, + &caller_oid))) + return(0); + + code = krb5_parse_name(kcontext, (char *) caller_buf.value, + &caller_princ); + + gss_release_buffer(&emin, &caller_buf); + + if (code) + return(code); + + retval = 0; + if (aentry = acl_find_entry(kcontext, caller_princ, principal)) { + if ((aentry->ae_op_allowed & opmask) == opmask) + retval = 1; + } + + krb5_free_principal(kcontext, caller_princ); + + DPRINT(DEBUG_CALLS, acl_debug_level, ("X acl_op_permitted()=%d\n", + retval)); + return(retval); +} + +kadm5_ret_t kadm5_get_privs(void *server_handle, long *privs) +{ + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + /* this is impossible to do with the current interface. For now, + return all privs, which will confuse some clients, but not + deny any access to users of "smart" clients which try to cache */ + + *privs = ~0; + + return KADM5_OK; +} diff --git a/src/lib/kadm5/srv/server_acl.h b/src/lib/kadm5/srv/server_acl.h new file mode 100644 index 0000000..9dfc8da --- /dev/null +++ b/src/lib/kadm5/srv/server_acl.h @@ -0,0 +1,81 @@ +/* + * kadmin/v5server/kadm5_defs.h + * + * Copyright 1995 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + */ + +#ifndef SERVER_ACL_H__ +#define SERVER_ACL_H__ + +/* + * Debug definitions. + */ +#define DEBUG_SPROC 1 +#define DEBUG_OPERATION 2 +#define DEBUG_HOST 4 +#define DEBUG_REALM 8 +#define DEBUG_REQUESTS 16 +#define DEBUG_ACL 32 +#define DEBUG_PROTO 64 +#define DEBUG_CALLS 128 +#define DEBUG_NOSLAVES 256 +#ifdef DEBUG +#define DPRINT(l1, cl, al) if ((cl & l1) != 0) xprintf al +#else /* DEBUG */ +#define DPRINT(l1, cl, al) +#endif /* DEBUG */ +#define DLOG(l1, cl, msg) if ((cl & l1) != 0) \ + com_err(programname, 0, msg) + +/* + * Access control bits. + */ +#define ACL_ADD 1 +#define ACL_DELETE 2 +#define ACL_MODIFY 4 +#define ACL_CHANGEPW 8 +/* #define ACL_CHANGE_OWN_PW 16 */ +#define ACL_INQUIRE 32 +/* #define ACL_EXTRACT 64 */ +#define ACL_LIST 128 +#define ACL_RENAME (ACL_ADD+ACL_DELETE) + +#define ACL_ALL_MASK (ACL_ADD | \ + ACL_DELETE | \ + ACL_MODIFY | \ + ACL_CHANGEPW | \ + ACL_INQUIRE | \ + ACL_LIST) + +krb5_error_code acl_init + KRB5_PROTOTYPE((krb5_context, + int, + char *)); +void acl_finish + KRB5_PROTOTYPE((krb5_context, + int)); +krb5_boolean acl_check + KRB5_PROTOTYPE((krb5_context, + gss_name_t, + krb5_int32, + krb5_principal)); + +#endif /* SERVER_ACL_H__ */ diff --git a/src/lib/kadm5/srv/server_dict.c b/src/lib/kadm5/srv/server_dict.c new file mode 100644 index 0000000..6c0bcef --- /dev/null +++ b/src/lib/kadm5/srv/server_dict.c @@ -0,0 +1,199 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include <sys/types.h> +#include <sys/file.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> +#include <kadm5/admin.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <malloc.h> +#include <memory.h> +#include <syslog.h> +#include "server_internal.h" + +static char **word_list = NULL; /* list of word pointers */ +static char *word_block = NULL; /* actual word data */ +static int word_count = 0; /* number of words */ +extern int errno; + +/* + * Function: word_compare + * + * Purpose: compare two words in the dictionary. + * + * Arguments: + * w1 (input) pointer to first word + * w2 (input) pointer to second word + * <return value> result of strcmp + * + * Requires: + * w1 and w2 to point to valid memory + * + */ + +static int +word_compare(const void *s1, const void *s2) +{ + return (strcasecmp(*(char **)s1, *(char **)s2)); +} + +/* + * Function: init-dict + * + * Purpose: Initialize in memory word dictionary + * + * Arguments: + * none + * <return value> KADM5_OK on sucsess errno on failure; + * (but success on ENOENT) + * + * Requires: + * If WORDFILE exists, it must contain a list of words, + * one word per-line. + * + * Effects: + * If WORDFILE exists, it is read into memory sorted for future + * use. If it does not exist, it syslogs an error message and returns + * success. + * + * Modifies: + * word_list to point to a chunck of allocated memory containing + * pointers to words + * word_block to contain the dictionary. + * + */ + +int init_dict(kadm5_config_params *params) +{ + int fd, + len, + i; + char *p, + *t; + struct stat sb; + + if(word_list != NULL && word_block != NULL) + return KADM5_OK; + if (! (params->mask & KADM5_CONFIG_DICT_FILE)) { + syslog(LOG_INFO, "No dictionary file specified, continuing " + "without one."); + return KADM5_OK; + } + if ((fd = open(params->dict_file, O_RDONLY)) == -1) { + if (errno == ENOENT) { + syslog(LOG_ERR, "WARNING! Cannot find dictionary file %s, " + "continuing without one.", params->dict_file); + return KADM5_OK; + } else + return errno; + } + if (fstat(fd, &sb) == -1) + return errno; + if ((word_block = (char *) malloc(sb.st_size + 1)) == NULL) + return errno; + if (read(fd, word_block, sb.st_size) != sb.st_size) + return errno; + (void) close(fd); + word_block[sb.st_size] = '\0'; + + p = word_block; + len = sb.st_size; + while(len > 0 && (t = memchr(p, '\n', len)) != NULL) { + *t = '\0'; + len -= t - p + 1; + p = t + 1; + word_count++; + } + if ((word_list = (char **) malloc(word_count * sizeof(char *))) == NULL) + return errno; + p = word_block; + for (i = 0; i < word_count; i++) { + word_list[i] = p; + p += strlen(p) + 1; + } + qsort(word_list, word_count, sizeof(char *), word_compare); + return KADM5_OK; +} + +/* + * Function: find_word + * + * Purpose: See if the specified word exists in the in-core dictionary + * + * Arguments: + * word (input) word to search for. + * <return value> WORD_NOT_FOUND if not in dictionary, + * KADM5_OK if if found word + * errno if init needs to be called and returns an + * error + * + * Requires: + * word to be a null terminated string. + * That word_list and word_block besetup + * + * Effects: + * finds word in dictionary. + * Modifies: + * nothing. + * + */ + +int +find_word(const char *word) +{ + char **value; + + if(word_list == NULL || word_block == NULL) + return WORD_NOT_FOUND; + if ((value = (char **) bsearch(&word, word_list, word_count, sizeof(char *), + word_compare)) == NULL) + return WORD_NOT_FOUND; + else + return KADM5_OK; +} + +/* + * Function: destroy_dict + * + * Purpose: destroy in-core copy of dictionary. + * + * Arguments: + * none + * <return value> none + * Requires: + * nothing + * Effects: + * frees up memory occupied by word_list and word_block + * sets count back to 0, and resets the pointers to NULL + * + * Modifies: + * word_list, word_block, and word_count. + * + */ + +void +destroy_dict(void) +{ + if(word_list) { + free(word_list); + word_list = NULL; + } + if(word_block) { + free(word_block); + word_block = NULL; + } + if(word_count) + word_count = 0; + return; +} diff --git a/src/lib/kadm5/srv/server_handle.c b/src/lib/kadm5/srv/server_handle.c new file mode 100644 index 0000000..53abe94 --- /dev/null +++ b/src/lib/kadm5/srv/server_handle.c @@ -0,0 +1,9 @@ +#include <krb5.h> +#include <kadm5/admin.h> +#include "server_internal.h" + +int _kadm5_check_handle(void *handle) +{ + CHECK_HANDLE(handle); + return 0; +} diff --git a/src/lib/kadm5/srv/server_init.c b/src/lib/kadm5/srv/server_init.c new file mode 100644 index 0000000..653f6d8 --- /dev/null +++ b/src/lib/kadm5/srv/server_init.c @@ -0,0 +1,330 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved. + * + * $Id$ + * $Source$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <com_err.h> +#include <kadm5/admin.h> +#include <krb5.h> +#include "server_internal.h" + +/* + * Function check_handle + * + * Purpose: Check a server handle and return a com_err code if it is + * invalid or 0 if it is valid. + * + * Arguments: + * + * handle The server handle. + */ + +static int check_handle(void *handle) +{ + CHECK_HANDLE(handle); + return 0; +} + +kadm5_ret_t kadm5_init_with_password(char *client_name, char *pass, + char *service_name, + kadm5_config_params *params, + krb5_ui_4 struct_version, + krb5_ui_4 api_version, + void **server_handle) +{ + return kadm5_init(client_name, pass, service_name, params, + struct_version, api_version, + server_handle); +} + +kadm5_ret_t kadm5_init_with_creds(char *client_name, + krb5_ccache ccache, + char *service_name, + kadm5_config_params *params, + krb5_ui_4 struct_version, + krb5_ui_4 api_version, + void **server_handle) +{ + /* + * A program calling init_with_creds *never* expects to prompt the + * user. Therefore, always pass a dummy password in case this is + * KADM5_API_VERSION_1. If this is KADM5_API_VERSION_2 and + * MKEY_FROM_KBD is non-zero, return an error. + */ + if (api_version == KADM5_API_VERSION_2 && params && + (params->mask & KADM5_CONFIG_MKEY_FROM_KBD) && + params->mkey_from_kbd) + return KADM5_BAD_SERVER_PARAMS; + return kadm5_init(client_name, NULL, service_name, params, + struct_version, api_version, + server_handle); +} + + +kadm5_ret_t kadm5_init_with_skey(char *client_name, char *keytab, + char *service_name, + kadm5_config_params *params, + krb5_ui_4 struct_version, + krb5_ui_4 api_version, + void **server_handle) +{ + /* + * A program calling init_with_skey *never* expects to prompt the + * user. Therefore, always pass a dummy password in case this is + * KADM5_API_VERSION_1. If this is KADM5_API_VERSION_2 and + * MKEY_FROM_KBD is non-zero, return an error. + */ + if (api_version == KADM5_API_VERSION_2 && params && + (params->mask & KADM5_CONFIG_MKEY_FROM_KBD) && + params->mkey_from_kbd) + return KADM5_BAD_SERVER_PARAMS; + return kadm5_init(client_name, NULL, service_name, params, + struct_version, api_version, + server_handle); +} + +kadm5_ret_t kadm5_init(char *client_name, char *pass, + char *service_name, + kadm5_config_params *params_in, + krb5_ui_4 struct_version, + krb5_ui_4 api_version, + void **server_handle) +{ + int ret; + kadm5_server_handle_t handle; + kadm5_config_params params_local; /* for v1 compat */ + + if (! server_handle) + return EINVAL; + + if (! client_name) + return EINVAL; + + if (! (handle = (kadm5_server_handle_t) malloc(sizeof *handle))) + return ENOMEM; + memset(handle, 0, sizeof(*handle)); + + if (ret = (int) krb5_init_context(&(handle->context))) { + free(handle); + return(ret); + } + + initialize_ovk_error_table(); + initialize_adb_error_table(); + initialize_ovku_error_table(); + krb5_init_ets(handle->context); + + handle->magic_number = KADM5_SERVER_HANDLE_MAGIC; + handle->struct_version = struct_version; + handle->api_version = api_version; + + /* + * Verify the version numbers before proceeding; we can't use + * CHECK_HANDLE because not all fields are set yet. + */ + GENERIC_CHECK_HANDLE(handle, KADM5_OLD_SERVER_API_VERSION, + KADM5_NEW_SERVER_API_VERSION); + + /* + * Acquire relevant profile entries. In version 2, merge values + * in params_in with values from profile, based on + * params_in->mask. + * + * In version 1, we've given a realm (which may be NULL) instead + * of params_in. So use that realm, make params_in contain an + * empty mask, and behave like version 2. + */ + memset((char *) ¶ms_local, 0, sizeof(params_local)); + if (api_version == KADM5_API_VERSION_1) { + params_local.realm = (char *) params_in; + if (params_in) + params_local.mask = KADM5_CONFIG_REALM; + params_in = ¶ms_local; + } + +#define ILLEGAL_PARAMS (KADM5_CONFIG_ADMIN_SERVER) + if (params_in && (params_in->mask & ILLEGAL_PARAMS)) { + krb5_free_context(handle->context); + free(handle); + return KADM5_BAD_SERVER_PARAMS; + } + + if (ret = kadm5_get_config_params(handle->context, + (char *) NULL, + (char *) NULL, + params_in, + &handle->params)) { + krb5_free_context(handle->context); + free(handle); + return(ret); + } + +#define REQUIRED_PARAMS (KADM5_CONFIG_REALM | KADM5_CONFIG_DBNAME | \ + KADM5_CONFIG_ADBNAME | \ + KADM5_CONFIG_ADB_LOCKFILE | \ + KADM5_CONFIG_ENCTYPE | \ + KADM5_CONFIG_FLAGS | \ + KADM5_CONFIG_MAX_LIFE | KADM5_CONFIG_MAX_RLIFE | \ + KADM5_CONFIG_EXPIRATION | KADM5_CONFIG_ENCTYPES) + + if ((handle->params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) { + krb5_free_context(handle->context); + free(handle); + return KRB5_CONFIG_BADFORMAT; + } + + /* + * Set the db_name based on configuration before calling + * krb5_db_init, so it will get used. + */ + if (ret = krb5_dbm_db_set_name(handle->context, + handle->params.dbname)) { + free(handle); + return(ret); + } + + if (ret = krb5_db_init(handle->context)) { + krb5_free_context(handle->context); + free(handle); + return(ret); + } + + if ((ret = krb5_parse_name(handle->context, client_name, + &handle->current_caller))) { + krb5_db_fini(handle->context); + krb5_free_context(handle->context); + free(handle); + return ret; + } + + if (! (handle->lhandle = malloc(sizeof(*handle)))) { + krb5_db_fini(handle->context); + krb5_free_context(handle->context); + free(handle); + return ENOMEM; + } + *handle->lhandle = *handle; + handle->lhandle->api_version = KADM5_API_VERSION_2; + handle->lhandle->struct_version = KADM5_STRUCT_VERSION; + handle->lhandle->lhandle = handle->lhandle; + + /* can't check the handle until current_caller is set */ + if (ret = check_handle((void *) handle)) { + free(handle); + return ret; + } + + /* + * The KADM5_API_VERSION_1 spec said "If pass (or keytab) is NULL + * or an empty string, reads the master password from [the stash + * file]. Otherwise, the non-NULL password is ignored and the + * user is prompted for it via the tty." However, the code was + * implemented the other way: when a non-NULL password was + * provided, the stash file was used. This is somewhat more + * sensible, as then a local or remote client that provides a + * password does not prompt the user. This code maintains the + * previous actual behavior, and not the old spec behavior, + * because that is how the unit tests are written. + * + * In KADM5_API_VERSION_2, this decision is controlled by + * params. + * + * kdb_init_master's third argument is "from_keyboard". + */ + if (ret = kdb_init_master(handle, handle->params.realm, + (handle->api_version == KADM5_API_VERSION_1 ? + ((pass == NULL) || !(strlen(pass))) : + ((handle->params.mask & + KADM5_CONFIG_MKEY_FROM_KBD) && + handle->params.mkey_from_kbd)) + )) { + krb5_db_fini(handle->context); + krb5_free_context(handle->context); + free(handle); + return ret; + } + + if ((ret = kdb_init_hist(handle, handle->params.realm))) { + krb5_db_fini(handle->context); + krb5_free_context(handle->context); + free(handle); + return ret; + } + + if (ret = init_dict(&handle->params)) { + krb5_db_fini(handle->context); + krb5_free_principal(handle->context, handle->current_caller); + krb5_free_context(handle->context); + free(handle); + return ret; + } + + if (ret = adb_policy_init(handle)) { + krb5_db_fini(handle->context); + krb5_free_principal(handle->context, handle->current_caller); + krb5_free_context(handle->context); + free(handle); + return ret; + } + handle->lhandle->policy_db = handle->policy_db; + + *server_handle = (void *) handle; + + return KADM5_OK; +} + +kadm5_ret_t kadm5_destroy(void *server_handle) +{ + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + destroy_dict(); + + adb_policy_close(handle); + krb5_db_fini(handle->context); + krb5_free_principal(handle->context, handle->current_caller); + krb5_free_context(handle->context); + handle->magic_number = 0; + free(handle->lhandle); + free(handle); + + return KADM5_OK; +} + +kadm5_ret_t kadm5_flush(void *server_handle) +{ + kadm5_server_handle_t handle = server_handle; + kadm5_ret_t ret; + + CHECK_HANDLE(server_handle); + + if ((ret = krb5_db_fini(handle->context)) || + /* + * Set the db_name based on configuration before calling + * krb5_db_init, so it will get used. + */ + (ret = krb5_dbm_db_set_name(handle->context, + handle->params.dbname)) || + (ret = krb5_db_init(handle->context)) || + (ret = adb_policy_close(handle)) || + (ret = adb_policy_init(handle))) { + (void) kadm5_destroy(server_handle); + return ret; + } + return KADM5_OK; +} + +int _kadm5_check_handle(void *handle) +{ + CHECK_HANDLE(handle); + return 0; +} diff --git a/src/lib/kadm5/srv/server_kdb.c b/src/lib/kadm5/srv/server_kdb.c new file mode 100644 index 0000000..1a900a3 --- /dev/null +++ b/src/lib/kadm5/srv/server_kdb.c @@ -0,0 +1,424 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include <stdio.h> +#include <stdlib.h> +#include "k5-int.h" +#include <kadm5/admin.h> +#include "server_internal.h" + +krb5_principal master_princ; +krb5_encrypt_block master_encblock; +krb5_keyblock master_keyblock; +krb5_db_entry master_db; + +krb5_principal hist_princ; +krb5_encrypt_block hist_encblock; +krb5_keyblock hist_key; +krb5_db_entry hist_db; +krb5_kvno hist_kvno; + +/* much of this code is stolen from the kdc. there should be some + library code to deal with this. */ + +krb5_error_code kdb_init_master(kadm5_server_handle_t handle, + char *r, int from_keyboard) +{ + int ret = 0; + char *realm; + krb5_keyblock tmk; + + if (r == NULL) { + if ((ret = krb5_get_default_realm(handle->context, &realm))) + return ret; + } else { + realm = r; + } + + if ((ret = krb5_db_setup_mkey_name(handle->context, + handle->params.mkey_name, + realm, NULL, &master_princ))) + goto done; + + master_keyblock.enctype = handle->params.enctype; + + krb5_use_enctype(handle->context, &master_encblock, + master_keyblock.enctype); + + if (ret = krb5_db_fetch_mkey(handle->context, master_princ, + &master_encblock, from_keyboard, + FALSE /* only prompt once */, + handle->params.stash_file, + NULL /* I'm not sure about this, + but it's what the kdc does --marc */, + &master_keyblock)) + goto done; + + if ((ret = krb5_db_init(handle->context)) != KSUCCESS) + goto done; + + if ((ret = krb5_db_verify_master_key(handle->context, master_princ, + &master_keyblock, + &master_encblock))) { + krb5_db_fini(handle->context); + return ret; + } + + /* the kdc gets the db mkvno here. The admin server never uses this + bit of information, so there's no reason to retrieve it. */ + + if ((ret = krb5_process_key(handle->context, &master_encblock, + &master_keyblock))) { + krb5_db_fini(handle->context); + goto done; + } + +done: + if (r == NULL) + free(realm); + + return(ret); +} + +/* + * Function: kdb_init_hist + * + * Purpose: Initializes the global history variables. + * + * Arguments: + * + * handle (r) kadm5 api server handle + * r (r) realm of history principal to use, or NULL + * + * Effects: This function sets the value of the following global + * variables: + * + * hist_princ krb5_principal holding the history principal + * hist_db krb5_db_entry of the history principal + * hist_key krb5_keyblock holding the history principal's key + * hist_encblock krb5_encrypt_block holding the procssed hist_key + * hist_kvno the version number of the history key + * + * If the history principal does not already exist, this function + * attempts to create it with kadm5_create_principal. WARNING! + * If the history principal is deleted and this function is executed + * (by kadmind, or kadmin.local, or anything else with permission), + * the principal will be assigned a new random key and all existing + * password history information will become useless. + */ +krb5_error_code kdb_init_hist(kadm5_server_handle_t handle, char *r) +{ + int ret = 0; + char *realm, *hist_name; + krb5_key_data *key_data; + + if (r == NULL) { + if ((ret = krb5_get_default_realm(handle->context, &realm))) + return ret; + } else { + realm = r; + } + + if ((hist_name = (char *) malloc(strlen(KADM5_HIST_PRINCIPAL) + + strlen(realm) + 2)) == NULL) + goto done; + + (void) sprintf(hist_name, "%s@%s", KADM5_HIST_PRINCIPAL, realm); + + if ((ret = krb5_parse_name(handle->context, hist_name, &hist_princ))) + goto done; + + if ((ret = kdb_get_entry(handle, hist_princ, &hist_db, NULL))) { + kadm5_principal_ent_rec ent; + + if (ret != KADM5_UNK_PRINC) + goto done; + + /* try to create the principal */ + + memset(&ent, 0, sizeof(ent)); + + ent.principal = hist_princ; + ent.max_life = KRB5_KDB_DISALLOW_ALL_TIX; + ent.attributes = 0; + + /* this uses hist_kvno. So we set it to 2, which will be the + correct value once the principal is created and randomized. + Of course, it doesn't make sense to keep a history for the + history principal, anyway. */ + + hist_kvno = 2; + + if (ret = kadm5_create_principal(handle, &ent, + (KADM5_PRINCIPAL | + KADM5_MAX_LIFE | + KADM5_ATTRIBUTES), + "to-be-random")) + goto done; + + /* this won't let us randomize the hist_princ. So we cheat. */ + + hist_princ = NULL; + + ret = kadm5_randkey_principal(handle, ent.principal, NULL, NULL); + + hist_princ = ent.principal; + + if (ret) + goto done; + + /* now read the newly-created kdb record out of the + database. */ + + if ((ret = kdb_get_entry(handle, hist_princ, &hist_db, NULL))) + goto done; + + } + + if (ret = krb5_dbe_find_enctype(handle->context, + &hist_db, + handle->params.enctype, + -1, + -1, + &key_data)) + goto done; + + if (ret = krb5_dbekd_decrypt_key_data(handle->context, &master_encblock, + key_data, &hist_key, NULL)) + goto done; + + krb5_use_enctype(handle->context, &hist_encblock, hist_key.enctype); + + if ((ret = krb5_process_key(handle->context, &hist_encblock, + &hist_key)) != KSUCCESS) + goto done; + + hist_kvno = key_data->key_data_kvno; + +done: + free(hist_name); + if (r == NULL) + free(realm); + return ret; +} + +/* + * Function: kdb_get_entry + * + * Purpose: Gets an entry from the kerberos database and breaks + * it out into a krb5_db_entry and an osa_princ_ent_t. + * + * Arguments: + * + * handle (r) the server_handle + * principal (r) the principal to get + * kdb (w) krb5_db_entry to fill in + * adb (w) osa_princ_ent_rec to fill in + * + * when the caller is done with kdb and adb, kdb_free_entry must be + * called to release them. The adb record is filled in with the + * contents of the KRB5_TL_KADM_DATA record; if that record doesn't + * exist, an empty but valid adb record is returned. + */ +krb5_error_code +kdb_get_entry(kadm5_server_handle_t handle, + krb5_principal principal, krb5_db_entry *kdb, + osa_princ_ent_rec *adb) +{ + krb5_error_code ret; + int nprincs; + krb5_boolean more; + krb5_tl_data tl_data; + XDR xdrs; + + if (ret = krb5_db_get_principal(handle->context, principal, kdb, &nprincs, + &more)) + return(ret); + + if (more) { + krb5_db_free_principal(handle->context, kdb, nprincs); + return(KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE); + } else if (nprincs != 1) { + krb5_db_free_principal(handle->context, kdb, nprincs); + return(KADM5_UNK_PRINC); + } + + if (adb) { + memset(adb, 0, sizeof(*adb)); + + tl_data.tl_data_type = KRB5_TL_KADM_DATA; + /* + * XXX Currently, lookup_tl_data always returns zero; it sets + * tl_data->tl_data_length to zero if the type isn't found. + * This should be fixed... + */ + if ((ret = krb5_dbe_lookup_tl_data(handle->context, kdb, &tl_data)) + || (tl_data.tl_data_length == 0)) { + /* there's no admin data. this can happen, if the admin + server is put into production after some principals + are created. In this case, return valid admin + data (which is all zeros with the hist_kvno filled + in), and when the entry is written, the admin + data will get stored correctly. */ + + adb->admin_history_kvno = hist_kvno; + + return(ret); + } + + xdrmem_create(&xdrs, tl_data.tl_data_contents, + tl_data.tl_data_length, XDR_DECODE); + if (! xdr_osa_princ_ent_rec(&xdrs, adb)) { + xdr_destroy(&xdrs); + krb5_db_free_principal(handle->context, kdb, 1); + return(OSA_ADB_XDR_FAILURE); + } + xdr_destroy(&xdrs); + } + + return(0); +} + +/* + * Function: kdb_free_entry + * + * Purpose: frees the resources allocated by kdb_get_entry + * + * Arguments: + * + * handle (r) the server_handle + * kdb (w) krb5_db_entry to fill in + * adb (w) osa_princ_ent_rec to fill in + * + * when the caller is done with kdb and adb, kdb_free_entry must be + * called to release them. + */ + +krb5_error_code +kdb_free_entry(kadm5_server_handle_t handle, + krb5_db_entry *kdb, osa_princ_ent_rec *adb) +{ + XDR xdrs; + + + if (kdb) + krb5_db_free_principal(handle->context, kdb, 1); + + if (adb) { + xdrmem_create(&xdrs, NULL, 0, XDR_FREE); + xdr_osa_princ_ent_rec(&xdrs, adb); + xdr_destroy(&xdrs); + } + + return(0); +} + +/* + * Function: kdb_put_entry + * + * Purpose: Stores the osa_princ_ent_t and krb5_db_entry into to + * database. + * + * Arguments: + * + * handle (r) the server_handle + * kdb (r/w) the krb5_db_entry to store + * adb (r) the osa_princ_db_ent to store + * + * Effects: + * + * The last modifier field of the kdb is set to the caller at now. + * adb is encoded with xdr_osa_princ_ent_ret and stored in kbd as + * KRB5_TL_KADM_DATA. kdb is then written to the database. + */ +krb5_error_code +kdb_put_entry(kadm5_server_handle_t handle, + krb5_db_entry *kdb, osa_princ_ent_rec *adb) +{ + krb5_error_code ret; + krb5_int32 now; + XDR xdrs; + krb5_tl_data tl_data; + int one; + + if (ret = krb5_timeofday(handle->context, &now)) + return(ret); + + if (ret = krb5_dbe_update_mod_princ_data(handle->context, kdb, now, + handle->current_caller)) + return(ret); + + xdralloc_create(&xdrs, XDR_ENCODE); + if(! xdr_osa_princ_ent_rec(&xdrs, adb)) { + xdr_destroy(&xdrs); + return(OSA_ADB_XDR_FAILURE); + } + tl_data.tl_data_type = KRB5_TL_KADM_DATA; + tl_data.tl_data_length = xdr_getpos(&xdrs); + tl_data.tl_data_contents = xdralloc_getdata(&xdrs); + + ret = krb5_dbe_update_tl_data(handle->context, kdb, &tl_data); + + xdr_destroy(&xdrs); + + if (ret) + return(ret); + + one = 1; + + if (ret = krb5_db_put_principal(handle->context, kdb, &one)) + return(ret); + + return(0); +} + +krb5_error_code +kdb_delete_entry(kadm5_server_handle_t handle, krb5_principal name) +{ + int one = 1; + krb5_error_code ret; + + ret = krb5_db_delete_principal(handle->context, name, &one); + + return ret; +} + +typedef struct _iter_data { + void (*func)(void *, krb5_principal); + void *data; +} iter_data; + +static krb5_error_code +kdb_iter_func(krb5_pointer data, krb5_db_entry *kdb) +{ + iter_data *id = (iter_data *) data; + + (*(id->func))(id->data, kdb->princ); + + return(0); +} + +krb5_error_code +kdb_iter_entry(kadm5_server_handle_t handle, + void (*iter_fct)(void *, krb5_principal), void *data) +{ + iter_data id; + krb5_error_code ret; + + id.func = iter_fct; + id.data = data; + + if (ret = krb5_db_iterate(handle->context, kdb_iter_func, &id)) + return(ret); + + return(0); +} + + diff --git a/src/lib/kadm5/srv/server_misc.c b/src/lib/kadm5/srv/server_misc.c new file mode 100644 index 0000000..24f101c --- /dev/null +++ b/src/lib/kadm5/srv/server_misc.c @@ -0,0 +1,101 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include "k5-int.h" +#include <krb5/kdb.h> +#include <ctype.h> +#include "adb.h" + +/* for strcasecmp */ +#include <string.h> + +#include "server_internal.h" + +kadm5_ret_t +adb_policy_init(kadm5_server_handle_t handle) +{ + osa_adb_ret_t ret; + if(handle->policy_db == (osa_adb_policy_t) NULL) + if((ret = osa_adb_open_policy(&handle->policy_db, + &handle->params)) != OSA_ADB_OK) + return ret; + return KADM5_OK; +} + +kadm5_ret_t +adb_policy_close(kadm5_server_handle_t handle) +{ + osa_adb_ret_t ret; + if(handle->policy_db != (osa_adb_policy_t) NULL) + if((ret = osa_adb_close_policy(handle->policy_db)) != OSA_ADB_OK) + return ret; + handle->policy_db = NULL; + return KADM5_OK; +} + +/* some of this is stolen from gatekeeper ... */ +kadm5_ret_t +passwd_check(kadm5_server_handle_t handle, + char *password, int use_policy, kadm5_policy_ent_t pol, + krb5_principal principal) +{ + int nupper = 0, + nlower = 0, + ndigit = 0, + npunct = 0, + nspec = 0; + char c, *s; + + if(use_policy) { + if(strlen(password) < pol->pw_min_length) + return KADM5_PASS_Q_TOOSHORT; + s = password; + while ((c = *s++)) { + if (islower(c)) { + nlower = 1; + continue; + } + else if (isupper(c)) { + nupper = 1; + continue; + } else if (isdigit(c)) { + ndigit = 1; + continue; + } else if (ispunct(c)) { + npunct = 1; + continue; + } else { + nspec = 1; + continue; + } + } + if ((nupper + nlower + ndigit + npunct + nspec) < pol->pw_min_classes) + return KADM5_PASS_Q_CLASS; + if((find_word(password) == KADM5_OK)) + return KADM5_PASS_Q_DICT; + else { + char *cp; + int c, n = krb5_princ_size(handle->context, principal); + cp = krb5_princ_realm(handle->context, principal)->data; + if (strcasecmp(cp, password) == 0) + return KADM5_PASS_Q_DICT; + for (c = 0; c < n ; c++) { + cp = krb5_princ_component(handle->context, principal, c)->data; + if (strcasecmp(cp, password) == 0) + return KADM5_PASS_Q_DICT; + } + return KADM5_OK; + } + } else { + if (strlen(password) < 1) + return KADM5_PASS_Q_TOOSHORT; + } + return KADM5_OK; +} diff --git a/src/lib/kadm5/srv/svr_chpass_util.c b/src/lib/kadm5/srv/svr_chpass_util.c new file mode 100644 index 0000000..df2bf4c --- /dev/null +++ b/src/lib/kadm5/srv/svr_chpass_util.c @@ -0,0 +1,15 @@ +#include <kadm5/admin.h> +#include "server_internal.h" + +kadm5_ret_t kadm5_chpass_principal_util(void *server_handle, + krb5_principal princ, + char *new_pw, + char **ret_pw, + char *msg_ret) +{ + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + return _kadm5_chpass_principal_util(handle, handle->lhandle, princ, + new_pw, ret_pw, msg_ret); +} diff --git a/src/lib/kadm5/srv/svr_iters.c b/src/lib/kadm5/srv/svr_iters.c new file mode 100644 index 0000000..19c9000 --- /dev/null +++ b/src/lib/kadm5/srv/svr_iters.c @@ -0,0 +1,248 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#if defined(HAVE_COMPILE) && defined(HAVE_STEP) +#define SOLARIS_REGEXPS +#elif defined(HAVE_REGCOMP) && defined(HAVE_REGEXEC) +#define POSIX_REGEXPS +#elif defined(HAVE_RE_COMP) && defined(HAVE_RE_EXEC) +#define BSD_REGEXPS +#else +#error I cannot find any regexp functions +#endif + +#include <sys/types.h> +#include <string.h> +#include <kadm5/admin.h> +#include "adb.h" +#include <dyn.h> +#ifdef SOLARIS_REGEXPS +#include <regexpr.h> +#endif +#ifdef POSIX_REGEXPS +#include <regex.h> +#endif +#include <stdlib.h> + +#include "server_internal.h" + +struct iter_data { + krb5_context context; + DynObject matches; + char *exp; +#ifdef SOLARIS_REGEXPS + char *expbuf; +#endif +#ifdef POSIX_REGEXPS + regex_t preg; +#endif +}; + +/* + * Function: glob_to_regexp + * + * Arguments: + * + * glob (r) the shell-style glob (?*[]) to convert + * realm (r) the default realm to append, or NULL + * regexp (w) the ed-style regexp created from glob + * + * Effects: + * + * regexp is filled in with allocated memory contained a regular + * expression to be used with re_comp/compile that matches what the + * shell-style glob would match. If glob does not contain an "@" + * character and realm is not NULL, "@<realm>" is appended to the regexp. + * + * Conversion algorithm: + * + * quoted characters are copied quoted + * ? is converted to . + * * is converted to .* + * active characters are quoted: ^, $, . + * [ and ] are active but supported and have the same meaning, so + * they are copied + * other characters are copied + * regexp is anchored with ^ and $ + */ +kadm5_ret_t glob_to_regexp(char *glob, char *realm, char **regexp) +{ + int append_realm; + char *p; + + /* validate the glob */ + if (glob[strlen(glob)-1] == '\\') + return EINVAL; + + /* A character of glob can turn into two in regexp, plus ^ and $ */ + /* and trailing null. If glob has no @, also allocate space for */ + /* the realm. */ + append_realm = (realm != NULL) && (strchr(glob, '@') == NULL); + p = (char *) malloc(strlen(glob)*2+ 3 + + (append_realm ? (strlen(realm)+1) : 0)); + if (p == NULL) + return ENOMEM; + *regexp = p; + + *p++ = '^'; + while (*glob) { + switch (*glob) { + case '?': + *p++ = '.'; + break; + case '*': + *p++ = '.'; + *p++ = '*'; + break; + case '.': + case '^': + case '$': + *p++ = '\\'; + *p++ = *glob; + break; + case '\\': + *p++ = '\\'; + *p++ = ++*glob; + break; + default: + *p++ = *glob; + break; + } + glob++; + } + + if (append_realm) { + *p++ = '@'; + strcpy(p, realm); + p += strlen(realm); + } + + *p++ = '$'; + *p++ = '\0'; + return KADM5_OK; +} + +void get_either_iter(struct iter_data *data, char *name) +{ + if ( +#ifdef SOLARIS_REGEXPS + (step(name, data->expbuf) != 0) +#endif +#ifdef POSIX_REGEXPS + (regexec(&data->preg, name, 0, NULL, 0) == 0) +#endif +#ifdef BSD_REGEXPS + (re_exec(name) != 0) +#endif + ) + { + (void) DynAdd(data->matches, &name); + } else + free(name); +} + +void get_pols_iter(void *data, osa_policy_ent_t entry) +{ + char *name; + + if ((name = strdup(entry->name)) == NULL) + return; + get_either_iter(data, name); +} + +void get_princs_iter(void *data, krb5_principal princ) +{ + struct iter_data *id = (struct iter_data *) data; + char *name; + + if (krb5_unparse_name(id->context, princ, &name) != 0) + return; + get_either_iter(data, name); +} + +kadm5_ret_t kadm5_get_either(int princ, + void *server_handle, + char *exp, + char ***princs, + int *count) +{ + struct iter_data data; + char *msg, *regexp; + int ret; + kadm5_server_handle_t handle = server_handle; + + *count = 0; + if (exp == NULL) + exp = "*"; + + CHECK_HANDLE(server_handle); + + if ((ret = glob_to_regexp(exp, princ ? handle->params.realm : NULL, + ®exp)) != KADM5_OK) + return ret; + + if ( +#ifdef SOLARIS_REGEXPS + ((data.expbuf = compile(regexp, NULL, NULL)) == NULL) +#endif +#ifdef POSIX_REGEXPS + ((regcomp(&data.preg, regexp, REG_NOSUB)) != 0) +#endif +#ifdef BSD_REGEXPS + ((msg = (char *) re_comp(regexp)) != NULL) +#endif + ) + { + /* XXX syslog msg or regerr(regerrno) */ + free(regexp); + return EINVAL; + } + + if ((data.matches = DynCreate(sizeof(char *), -4)) == NULL) { + free(regexp); + return ENOMEM; + } + + if (princ) { + data.context = handle->context; + ret = kdb_iter_entry(handle, get_princs_iter, (void *) &data); + } else { + ret = osa_adb_iter_policy(handle->policy_db, get_pols_iter, (void *)&data); + } + + if (ret != OSA_ADB_OK) { + free(regexp); + DynDestroy(data.matches); + return ret; + } + + (*princs) = (char **) DynArray(data.matches); + *count = DynSize(data.matches); + DynRelease(data.matches); + free(regexp); + return KADM5_OK; +} + +kadm5_ret_t kadm5_get_principals(void *server_handle, + char *exp, + char ***princs, + int *count) +{ + return kadm5_get_either(1, server_handle, exp, princs, count); +} + +kadm5_ret_t kadm5_get_policies(void *server_handle, + char *exp, + char ***pols, + int *count) +{ + return kadm5_get_either(0, server_handle, exp, pols, count); +} + diff --git a/src/lib/kadm5/srv/svr_misc_free.c b/src/lib/kadm5/srv/svr_misc_free.c new file mode 100644 index 0000000..5c76a1e --- /dev/null +++ b/src/lib/kadm5/srv/svr_misc_free.c @@ -0,0 +1,37 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + * + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif +#include <kadm5/admin.h> +#include <malloc.h> +#include "server_internal.h" + +kadm5_ret_t +kadm5_free_principal_ent(void *server_handle, + kadm5_principal_ent_t val) +{ + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + if(val) { + if(val->principal) + krb5_free_principal(handle->context, val->principal); + if(val->mod_name) + krb5_free_principal(handle->context, val->mod_name); + if(val->policy) + free(val->policy); + + /* XXX free key_data and tl_data */ + + if (handle->api_version == KADM5_API_VERSION_1) + free(val); + } + return KADM5_OK; +} diff --git a/src/lib/kadm5/srv/svr_policy.c b/src/lib/kadm5/srv/svr_policy.c new file mode 100644 index 0000000..74e2521 --- /dev/null +++ b/src/lib/kadm5/srv/svr_policy.c @@ -0,0 +1,315 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include <sys/types.h> +#include <kadm5/admin.h> +#include "adb.h" +#include "server_internal.h" +#include <stdlib.h> + +#define MAX_PW_HISTORY 10 +#define MIN_PW_HISTORY 1 +#define MIN_PW_CLASSES 1 +#define MAX_PW_CLASSES 5 +#define MIN_PW_LENGTH 1 + +/* + * Function: kadm5_create_policy + * + * Purpose: Create Policies in the policy DB. + * + * Arguments: + * entry (input) The policy entry to be written out to the DB. + * mask (input) Specifies which fields in entry are to ge written out + * and which get default values. + * <return value> 0 if sucsessfull otherwise an error code is returned. + * + * Requires: + * Entry must be a valid principal entry, and mask have a valid value. + * + * Effects: + * Verifies that mask does not specify that the refcount should + * be set as part of the creation, and calls + * kadm5_create_policy_internal. If the refcount *is* + * specified, returns KADM5_BAD_MASK. + */ + +kadm5_ret_t +kadm5_create_policy(void *server_handle, + kadm5_policy_ent_t entry, long mask) +{ + CHECK_HANDLE(server_handle); + + if (mask & KADM5_REF_COUNT) + return KADM5_BAD_MASK; + else + return kadm5_create_policy_internal(server_handle, entry, mask); +} + +/* + * Function: kadm5_create_policy_internal + * + * Purpose: Create Policies in the policy DB. + * + * Arguments: + * entry (input) The policy entry to be written out to the DB. + * mask (input) Specifies which fields in entry are to ge written out + * and which get default values. + * <return value> 0 if sucsessfull otherwise an error code is returned. + * + * Requires: + * Entry must be a valid principal entry, and mask have a valid value. + * + * Effects: + * Writes the data to the database, and does a database sync if + * sucsessfull. + * + */ + +kadm5_ret_t +kadm5_create_policy_internal(void *server_handle, + kadm5_policy_ent_t entry, long mask) +{ + kadm5_server_handle_t handle = server_handle; + osa_policy_ent_rec pent; + int ret; + char *p; + + CHECK_HANDLE(server_handle); + + if ((entry == (kadm5_policy_ent_t) NULL) || (entry->policy == NULL)) + return EINVAL; + if(strlen(entry->policy) == 0) + return KADM5_BAD_POLICY; + if (!(mask & KADM5_POLICY)) + return KADM5_BAD_MASK; + + pent.name = entry->policy; + p = entry->policy; + while(*p != '\0') { + if(*p < ' ' || *p > '~') + return KADM5_BAD_POLICY; + else + p++; + } + if (!(mask & KADM5_PW_MAX_LIFE)) + pent.pw_max_life = 0; + else + pent.pw_max_life = entry->pw_max_life; + if (!(mask & KADM5_PW_MIN_LIFE)) + pent.pw_min_life = 0; + else { + if((mask & KADM5_PW_MAX_LIFE)) { + if(entry->pw_min_life > entry->pw_max_life && entry->pw_max_life != 0) + return KADM5_BAD_MIN_PASS_LIFE; + } + pent.pw_min_life = entry->pw_min_life; + } + if (!(mask & KADM5_PW_MIN_LENGTH)) + pent.pw_min_length = MIN_PW_LENGTH; + else { + if(entry->pw_min_length < MIN_PW_LENGTH) + return KADM5_BAD_LENGTH; + pent.pw_min_length = entry->pw_min_length; + } + if (!(mask & KADM5_PW_MIN_CLASSES)) + pent.pw_min_classes = MIN_PW_CLASSES; + else { + if(entry->pw_min_classes > MAX_PW_CLASSES || entry->pw_min_classes < MIN_PW_CLASSES) + return KADM5_BAD_CLASS; + pent.pw_min_classes = entry->pw_min_classes; + } + if (!(mask & KADM5_PW_HISTORY_NUM)) + pent.pw_history_num = MIN_PW_HISTORY; + else { + if(entry->pw_history_num < MIN_PW_HISTORY || + entry->pw_history_num > MAX_PW_HISTORY) + return KADM5_BAD_HISTORY; + else + pent.pw_history_num = entry->pw_history_num; + } + if (!(mask & KADM5_REF_COUNT)) + pent.policy_refcnt = 0; + else + pent.policy_refcnt = entry->policy_refcnt; + if ((ret = osa_adb_create_policy(handle->policy_db, &pent)) == OSA_ADB_OK) + return KADM5_OK; + else + return ret; +} + +kadm5_ret_t +kadm5_delete_policy(void *server_handle, kadm5_policy_t name) +{ + kadm5_server_handle_t handle = server_handle; + osa_policy_ent_t entry; + int ret; + + CHECK_HANDLE(server_handle); + + if(name == (kadm5_policy_t) NULL) + return EINVAL; + if(strlen(name) == 0) + return KADM5_BAD_POLICY; + if ((ret = osa_adb_get_policy(handle->policy_db, name, &entry)) != OSA_ADB_OK) + return ret; + if(entry->policy_refcnt != 0) { + osa_free_policy_ent(entry); + return KADM5_POLICY_REF; + } + osa_free_policy_ent(entry); + if ((ret = osa_adb_destroy_policy(handle->policy_db, name)) == OSA_ADB_OK) + return KADM5_OK; + else + return ret; +} + +kadm5_ret_t +kadm5_modify_policy(void *server_handle, + kadm5_policy_ent_t entry, long mask) +{ + CHECK_HANDLE(server_handle); + + if (mask & KADM5_REF_COUNT) + return KADM5_BAD_MASK; + else + return kadm5_modify_policy_internal(server_handle, entry, mask); +} + +kadm5_ret_t +kadm5_modify_policy_internal(void *server_handle, + kadm5_policy_ent_t entry, long mask) +{ + kadm5_server_handle_t handle = server_handle; + osa_policy_ent_t p; + int ret; + + CHECK_HANDLE(server_handle); + + if((entry == (kadm5_policy_ent_t) NULL) || (entry->policy == NULL)) + return EINVAL; + if(strlen(entry->policy) == 0) + return KADM5_BAD_POLICY; + if((mask & KADM5_POLICY)) + return KADM5_BAD_MASK; + + switch ((ret = osa_adb_get_policy(handle->policy_db, entry->policy, &p))) { + case OSA_ADB_OK: + break; + case OSA_ADB_NOENT: + return KADM5_UNK_POLICY; + default: + break; + } + if ((mask & KADM5_PW_MAX_LIFE)) + p->pw_max_life = entry->pw_max_life; + if ((mask & KADM5_PW_MIN_LIFE)) { + if(entry->pw_min_life > p->pw_max_life && p->pw_max_life != 0) { + osa_free_policy_ent(p); + return KADM5_BAD_MIN_PASS_LIFE; + } + p->pw_min_life = entry->pw_min_life; + } + if ((mask & KADM5_PW_MIN_LENGTH)) { + if(entry->pw_min_length < MIN_PW_LENGTH) { + osa_free_policy_ent(p); + return KADM5_BAD_LENGTH; + } + p->pw_min_length = entry->pw_min_length; + } + if ((mask & KADM5_PW_MIN_CLASSES)) { + if(entry->pw_min_classes > MAX_PW_CLASSES || + entry->pw_min_classes < MIN_PW_CLASSES) { + osa_free_policy_ent(p); + return KADM5_BAD_CLASS; + } + p->pw_min_classes = entry->pw_min_classes; + } + if ((mask & KADM5_PW_HISTORY_NUM)) { + if(entry->pw_history_num < MIN_PW_HISTORY || + entry->pw_history_num > MAX_PW_HISTORY) { + osa_free_policy_ent(p); + return KADM5_BAD_HISTORY; + } + p->pw_history_num = entry->pw_history_num; + } + if ((mask & KADM5_REF_COUNT)) + p->policy_refcnt = entry->policy_refcnt; + switch ((ret = osa_adb_put_policy(handle->policy_db, p))) { + case OSA_ADB_OK: + ret = KADM5_OK; + break; + case OSA_ADB_NOENT: /* this should not happen here ... */ + ret = KADM5_UNK_POLICY; + break; + } + osa_free_policy_ent(p); + return ret; +} + +kadm5_ret_t +kadm5_get_policy(void *server_handle, kadm5_policy_t name, + kadm5_policy_ent_t entry) +{ + osa_policy_ent_t t; + kadm5_policy_ent_rec entry_local, **entry_orig, *new; + int ret; + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + /* + * In version 1, entry is a pointer to a kadm5_policy_ent_t that + * should be filled with allocated memory. + */ + if (handle->api_version == KADM5_API_VERSION_1) { + entry_orig = (kadm5_policy_ent_rec **) entry; + *entry_orig = NULL; + entry = &entry_local; + } + + if (name == (kadm5_policy_t) NULL) + return EINVAL; + if(strlen(name) == 0) + return KADM5_BAD_POLICY; + switch((ret = osa_adb_get_policy(handle->policy_db, name, &t))) { + case OSA_ADB_OK: + break; + case OSA_ADB_NOENT: + return KADM5_UNK_POLICY; + default: + return ret; + } + if ((entry->policy = (char *) malloc(strlen(t->name) + 1)) == NULL) { + osa_free_policy_ent(t); + return ENOMEM; + } + strcpy(entry->policy, t->name); + entry->pw_min_life = t->pw_min_life; + entry->pw_max_life = t->pw_max_life; + entry->pw_min_length = t->pw_min_length; + entry->pw_min_classes = t->pw_min_classes; + entry->pw_history_num = t->pw_history_num; + entry->policy_refcnt = t->policy_refcnt; + osa_free_policy_ent(t); + + if (handle->api_version == KADM5_API_VERSION_1) { + new = (kadm5_policy_ent_t) malloc(sizeof(kadm5_policy_ent_rec)); + if (new == NULL) { + free(entry->policy); + osa_free_policy_ent(t); + return ENOMEM; + } + *new = *entry; + *entry_orig = new; + } + + return KADM5_OK; +} diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c new file mode 100644 index 0000000..6f9671f --- /dev/null +++ b/src/lib/kadm5/srv/svr_principal.c @@ -0,0 +1,1350 @@ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include <sys/types.h> +#include <sys/time.h> +#include <kadm5/admin.h> +#include "adb.h" +#include "k5-int.h" +#include <krb5/kdb.h> +#include <stdio.h> +#include <string.h> +#include "server_internal.h" +#include <stdarg.h> +#include <stdlib.h> + +extern krb5_principal master_princ; +extern krb5_principal hist_princ; +extern krb5_encrypt_block master_encblock; +extern krb5_encrypt_block hist_encblock; +extern krb5_keyblock master_keyblock; +extern krb5_keyblock hist_key; +extern krb5_db_entry master_db; +extern krb5_db_entry hist_db; +extern krb5_kvno hist_kvno; + +static int decrypt_key_data(krb5_context context, + int n_key_data, krb5_key_data *key_data, + krb5_keyblock **keyblocks, int *n_keys); + +/* + * XXX Functions that ought to be in libkrb5.a, but aren't. + */ +int krb5_free_keyblock_contents(context, key) + krb5_context context; + krb5_keyblock *key; +{ + memset(key->contents, 0, key->length); + krb5_xfree(key->contents); + return 0; +} + +kadm5_ret_t krb5_copy_key_data_contents(context, from, to) + krb5_context context; + krb5_key_data *from, *to; +{ + int i, idx; + + *to = *from; + + idx = (from->key_data_ver == 1 ? 1 : 2); + + for (i = 0; i < idx; i++) { + to->key_data_contents[i] = malloc(from->key_data_length[i]); + if (to->key_data_contents[i] == NULL) { + for (i = 0; i < idx; i++) { + if (to->key_data_contents[i]) { + memset(to->key_data_contents[i], 0, + to->key_data_length[i]); + free(to->key_data_contents[i]); + } + } + return ENOMEM; + } + memcpy(to->key_data_contents[i], from->key_data_contents[i], + from->key_data_length[i]); + } + return 0; +} + +static krb5_tl_data *dup_tl_data(krb5_tl_data *tl) +{ + krb5_tl_data *n; + + n = (krb5_tl_data *) malloc(sizeof(krb5_tl_data)); + if (n == NULL) + return NULL; + n->tl_data_contents = malloc(tl->tl_data_length); + if (n->tl_data_contents == NULL) { + free(n); + return NULL; + } + memcpy(n->tl_data_contents, tl->tl_data_contents, tl->tl_data_length); + n->tl_data_type = tl->tl_data_type; + n->tl_data_length = tl->tl_data_length; + n->tl_data_next = NULL; + return n; +} + +kadm5_ret_t +kadm5_create_principal(void *server_handle, + kadm5_principal_ent_t entry, long mask, + char *password) +{ + krb5_db_entry kdb; + osa_princ_ent_rec adb; + kadm5_policy_ent_rec polent; + krb5_int32 now; + krb5_tl_data *tl_data_orig, *tl_data_tail; + unsigned int ret; + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + /* + * Argument sanity checking, and opening up the DB + */ + if(!(mask & KADM5_PRINCIPAL) || (mask & KADM5_MOD_NAME) || + (mask & KADM5_MOD_TIME) || (mask & KADM5_LAST_PWD_CHANGE) || + (mask & KADM5_MKVNO) || (mask & KADM5_POLICY_CLR) || + (mask & KADM5_AUX_ATTRIBUTES) || (mask & KADM5_KEY_DATA) || + (mask & KADM5_LAST_SUCCESS) || (mask & KADM5_LAST_FAILED) || + (mask & KADM5_FAIL_AUTH_COUNT)) + return KADM5_BAD_MASK; + if((mask & ~ALL_PRINC_MASK)) + return KADM5_BAD_MASK; + if (entry == (kadm5_principal_ent_t) NULL || password == NULL) + return EINVAL; + + /* + * Check to see if the principal exists + */ + ret = kdb_get_entry(handle, entry->principal, &kdb, &adb); + + switch(ret) { + case KADM5_UNK_PRINC: + break; + case 0: + kdb_free_entry(handle, &kdb, &adb); + return KADM5_DUP; + default: + return ret; + } + + memset(&kdb, 0, sizeof(krb5_db_entry)); + memset(&adb, 0, sizeof(osa_princ_ent_rec)); + + /* + * If a policy was specified, load it. + * If we can not find the one specified return an error + */ + if ((mask & KADM5_POLICY)) { + if ((ret = kadm5_get_policy(handle->lhandle, entry->policy, + &polent)) != KADM5_OK) { + if(ret == EINVAL) + return KADM5_BAD_POLICY; + else + return ret; + } + } + if (ret = passwd_check(handle, password, (mask & KADM5_POLICY), + &polent, entry->principal)) { + if (mask & KADM5_POLICY) + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + return ret; + } + /* + * Start populating the various DB fields, using the + * "defaults" for fields that were not specified by the + * mask. + */ + if (ret = krb5_timeofday(handle->context, &now)) { + if (mask & KADM5_POLICY) + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + return ret; + } + + kdb.magic = KRB5_KDB_MAGIC_NUMBER; + kdb.len = KRB5_KDB_V1_BASE_LENGTH; /* gag me with a chainsaw */ + + if ((mask & KADM5_ATTRIBUTES)) + kdb.attributes = entry->attributes; + else + kdb.attributes = handle->params.flags; + + if ((mask & KADM5_MAX_LIFE)) + kdb.max_life = entry->max_life; + else + kdb.max_life = handle->params.max_life; + + if (mask & KADM5_MAX_RLIFE) + kdb.max_renewable_life = entry->max_renewable_life; + else + kdb.max_renewable_life = handle->params.max_rlife; + + if ((mask & KADM5_PRINC_EXPIRE_TIME)) + kdb.expiration = entry->princ_expire_time; + else + kdb.expiration = handle->params.expiration; + + kdb.pw_expiration = 0; + if ((mask & KADM5_POLICY)) { + if(polent.pw_max_life) + kdb.pw_expiration = now + polent.pw_max_life; + else + kdb.pw_expiration = 0; + } + if ((mask & KADM5_PW_EXPIRATION)) { + if(!kdb.pw_expiration) + kdb.pw_expiration = entry->pw_expiration; + else { + if(entry->pw_expiration != 0) + kdb.pw_expiration = (entry->pw_expiration < kdb.pw_expiration) ? + entry->pw_expiration : kdb.pw_expiration; + } + } + + kdb.last_success = 0; + kdb.last_failed = 0; + kdb.fail_auth_count = 0; + + /* this is kind of gross, but in order to free the tl data, I need + to free the entire kdb entry, and that will try to free the + principal. */ + + if (ret = krb5_copy_principal(handle->context, + entry->principal, &(kdb.princ))) { + if (mask & KADM5_POLICY) + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + return(ret); + } + + if (ret = krb5_dbe_update_last_pwd_change(handle->context, &kdb, now)) { + krb5_dbe_free_contents(handle->context, &kdb); + if (mask & KADM5_POLICY) + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + return(ret); + } + + /* initialize the keys */ + + if (ret = krb5_dbe_cpw(handle->context, &master_encblock, + handle->params.keysalts, + handle->params.num_keysalts, + password, + (mask & KADM5_KVNO)?entry->kvno:1, &kdb)) { + krb5_dbe_free_contents(handle->context, &kdb); + if (mask & KADM5_POLICY) + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + return(ret); + } + + /* populate the admin-server-specific fields. In the OV server, + this used to be in a separate database. Since there's already + marshalling code for the admin fields, to keep things simple, + I'm going to keep it, and make all the admin stuff occupy a + single tl_data record, */ + + adb.admin_history_kvno = hist_kvno; + if ((mask & KADM5_POLICY)) { + adb.aux_attributes = KADM5_POLICY; + + /* this does *not* need to be strdup'ed, because adb is xdr */ + /* encoded in osa_adb_create_princ, and not ever freed */ + + adb.policy = entry->policy; + } + + /* increment the policy ref count, if any */ + + if ((mask & KADM5_POLICY)) { + polent.policy_refcnt++; + if ((ret = kadm5_modify_policy_internal(handle->lhandle, &polent, + KADM5_REF_COUNT)) + != KADM5_OK) { + krb5_dbe_free_contents(handle->context, &kdb); + if (mask & KADM5_POLICY) + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + return(ret); + } + } + + if (mask & KADM5_TL_DATA) { + /* splice entry->tl_data onto the front of kdb.tl_data */ + tl_data_orig = kdb.tl_data; + for (tl_data_tail = entry->tl_data; tl_data_tail->tl_data_next; + tl_data_tail = tl_data_tail->tl_data_next) + ; + tl_data_tail->tl_data_next = kdb.tl_data; + kdb.tl_data = entry->tl_data; + } + + /* store the new db entry */ + ret = kdb_put_entry(handle, &kdb, &adb); + + if (mask & KADM5_TL_DATA) { + /* remove entry->tl_data from the front of kdb.tl_data */ + tl_data_tail->tl_data_next = NULL; + kdb.tl_data = tl_data_orig; + } + + krb5_dbe_free_contents(handle->context, &kdb); + + if (ret) { + if ((mask & KADM5_POLICY)) { + /* decrement the policy ref count */ + + polent.policy_refcnt--; + /* + * if this fails, there's nothing we can do anyway. the + * policy refcount wil be too high. + */ + (void) kadm5_modify_policy_internal(handle->lhandle, &polent, + KADM5_REF_COUNT); + } + + if (mask & KADM5_POLICY) + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + return(ret); + } + + if (mask & KADM5_POLICY) + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + + return KADM5_OK; +} + + +kadm5_ret_t +kadm5_delete_principal(void *server_handle, krb5_principal principal) +{ + unsigned int ret; + kadm5_policy_ent_rec polent; + krb5_db_entry kdb; + osa_princ_ent_rec adb; + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + if (principal == NULL) + return EINVAL; + + if (ret = kdb_get_entry(handle, principal, &kdb, &adb)) + return(ret); + + if ((adb.aux_attributes & KADM5_POLICY)) { + if ((ret = kadm5_get_policy(handle->lhandle, + adb.policy, &polent)) + == KADM5_OK) { + polent.policy_refcnt--; + if ((ret = kadm5_modify_policy_internal(handle->lhandle, &polent, + KADM5_REF_COUNT)) + != KADM5_OK) { + (void) kadm5_free_policy_ent(handle->lhandle, &polent); + kdb_free_entry(handle, &kdb, &adb); + return(ret); + } + } + if (ret = kadm5_free_policy_ent(handle->lhandle, &polent)) { + kdb_free_entry(handle, &kdb, &adb); + return ret; + } + } + + ret = kdb_delete_entry(handle, principal); + + kdb_free_entry(handle, &kdb, &adb); + + return ret; +} + +kadm5_ret_t +kadm5_modify_principal(void *server_handle, + kadm5_principal_ent_t entry, long mask) +{ + int ret, ret2, i; + kadm5_policy_ent_rec npol, opol; + int have_npol = 0, have_opol = 0; + krb5_db_entry kdb; + krb5_tl_data *tl_data_orig, *tl_data_tail; + osa_princ_ent_rec adb; + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + if((mask & KADM5_PRINCIPAL) || (mask & KADM5_LAST_PWD_CHANGE) || + (mask & KADM5_MOD_TIME) || (mask & KADM5_MOD_NAME) || + (mask & KADM5_MKVNO) || (mask & KADM5_AUX_ATTRIBUTES) || + (mask & KADM5_KEY_DATA) || (mask & KADM5_LAST_SUCCESS) || + (mask & KADM5_LAST_FAILED)) + return KADM5_BAD_MASK; + if((mask & ~ALL_PRINC_MASK)) + return KADM5_BAD_MASK; + if((mask & KADM5_POLICY) && (mask & KADM5_POLICY_CLR)) + return KADM5_BAD_MASK; + if(entry == (kadm5_principal_ent_t) NULL) + return EINVAL; + + if (ret = kdb_get_entry(handle, entry->principal, &kdb, &adb)) + return(ret); + + /* + * This is pretty much the same as create ... + */ + + if ((mask & KADM5_POLICY)) { + ret = kadm5_get_policy(handle->lhandle, entry->policy, &npol); + switch(ret) { + case EINVAL: + ret = KADM5_BAD_POLICY; + break; + case KADM5_UNK_POLICY: + case KADM5_BAD_POLICY: + ret = KADM5_UNK_POLICY; + goto done; + break; + case KADM5_OK: + have_npol = 1; + if(adb.aux_attributes & KADM5_POLICY) { + if(strcmp(adb.policy, entry->policy)) { + ret = kadm5_get_policy(handle->lhandle, + adb.policy, &opol); + switch(ret) { + case EINVAL: + case KADM5_BAD_POLICY: + case KADM5_UNK_POLICY: + break; + case KADM5_OK: + have_opol = 1; + opol.policy_refcnt--; + break; + default: + goto done; + break; + } + npol.policy_refcnt++; + } + } else npol.policy_refcnt++; + adb.aux_attributes |= KADM5_POLICY; + if (adb.policy) + free(adb.policy); + adb.policy = strdup(entry->policy); + if (npol.pw_max_life) { + if (ret = + krb5_dbe_lookup_last_pwd_change(handle->context, &kdb, + &(kdb.pw_expiration))) + goto done; + kdb.pw_expiration += npol.pw_max_life; + } else { + kdb.pw_expiration = 0; + } + break; + default: + goto done; + } + if ((mask & KADM5_PW_EXPIRATION)) { + if(kdb.pw_expiration == 0) + kdb.pw_expiration = entry->pw_expiration; + else if(entry->pw_expiration != 0) + kdb.pw_expiration = (entry->pw_expiration < kdb.pw_expiration) ? + entry->pw_expiration : kdb.pw_expiration; + } + } + if ((mask & KADM5_PW_EXPIRATION) && !(mask & KADM5_POLICY)) { + if(kdb.pw_expiration == 0) + kdb.pw_expiration = entry->pw_expiration; + else if(entry->pw_expiration != 0) + kdb.pw_expiration = (entry->pw_expiration < kdb.pw_expiration) ? + entry->pw_expiration : kdb.pw_expiration; + } + + if ((mask & KADM5_POLICY_CLR)) { + if (adb.aux_attributes & KADM5_POLICY) { + adb.aux_attributes &= ~KADM5_POLICY; + kdb.pw_expiration = 0; + ret = kadm5_get_policy(handle->lhandle, adb.policy, &opol); + switch(ret) { + case EINVAL: + case KADM5_BAD_POLICY: + case KADM5_UNK_POLICY: + ret = KADM5_BAD_DB; + goto done; + break; + case KADM5_OK: + have_opol = 1; + if (adb.policy) + free(adb.policy); + adb.policy = NULL; + opol.policy_refcnt--; + break; + default: + goto done; + break; + } + } + } + if (((mask & KADM5_POLICY) || + (mask & KADM5_POLICY_CLR)) && + (((have_opol) && + (ret = + kadm5_modify_policy_internal(handle->lhandle, &opol, + KADM5_REF_COUNT))) || + ((have_npol) && + (ret = + kadm5_modify_policy_internal(handle->lhandle, &npol, + KADM5_REF_COUNT))))) + goto done; + + if ((mask & KADM5_ATTRIBUTES)) + kdb.attributes = entry->attributes; + if ((mask & KADM5_MAX_LIFE)) + kdb.max_life = entry->max_life; + if ((mask & KADM5_PRINC_EXPIRE_TIME)) + kdb.expiration = entry->princ_expire_time; + /* the pw_expiration logic would go here if it wasn't spread + all over the policy code */ + if (mask & KADM5_MAX_RLIFE) + kdb.max_renewable_life = entry->max_renewable_life; + if (mask & KADM5_FAIL_AUTH_COUNT) + kdb.fail_auth_count = entry->fail_auth_count; + + if((mask & KADM5_KVNO)) { + for (i = 0; i < kdb.n_key_data; i++) + kdb.key_data[i].key_data_kvno = entry->kvno; + } + + if (mask & KADM5_TL_DATA) { + /* splice entry->tl_data onto the front of kdb.tl_data */ + tl_data_orig = kdb.tl_data; + for (tl_data_tail = entry->tl_data; tl_data_tail->tl_data_next; + tl_data_tail = tl_data_tail->tl_data_next) + ; + tl_data_tail->tl_data_next = kdb.tl_data; + kdb.tl_data = entry->tl_data; + } + + if ((ret = kdb_put_entry(handle, &kdb, &adb))) + goto done; + + if (mask & KADM5_TL_DATA) { + /* remove entry->tl_data from the front of kdb.tl_data */ + tl_data_tail->tl_data_next = NULL; + kdb.tl_data = tl_data_orig; + } + + ret = KADM5_OK; +done: + if (have_opol) { + ret2 = kadm5_free_policy_ent(handle->lhandle, &opol); + ret = ret ? ret : ret2; + } + if (have_npol) { + ret2 = kadm5_free_policy_ent(handle->lhandle, &npol); + ret = ret ? ret : ret2; + } + kdb_free_entry(handle, &kdb, &adb); + return ret; +} + +kadm5_ret_t +kadm5_rename_principal(void *server_handle, + krb5_principal source, krb5_principal target) +{ + krb5_db_entry kdb; + osa_princ_ent_rec adb; + int ret, i; + kadm5_server_handle_t handle = server_handle; + + CHECK_HANDLE(server_handle); + + if (source == NULL || target == NULL) + return EINVAL; + + if ((ret = kdb_get_entry(handle, target, &kdb, &adb)) == 0) { + kdb_free_entry(handle, &kdb, &adb); + return(KADM5_DUP); + } + + if ((ret = kdb_get_entry(handle, source, &kdb, &adb))) + return ret; + + /* this is kinda gross, but unavoidable */ + + for (i=0; i<kdb.n_key_data; i++) { + if ((kdb.key_data[i].key_data_ver == 1) || + (kdb.key_data[i].key_data_type[1] == KRB5_KDB_SALTTYPE_NORMAL)) { + ret = KADM5_NO_RENAME_SALT; + goto done; + } + } + + krb5_free_principal(handle->context, kdb.princ); + if (ret = krb5_copy_principal(handle->context, target, &kdb.princ)) { + kdb.princ = NULL; /* so freeing the dbe doesn't lose */ + goto done; + } + + if ((ret = kdb_put_entry(handle, &kdb, &adb))) + goto done; + + ret = kdb_delete_entry(handle, source); + +done: + kdb_free_entry(handle, &kdb, &adb); + return ret; +} + +kadm5_ret_t +kadm5_get_principal(void *server_handle, krb5_principal principal, + kadm5_principal_ent_t entry, + long in_mask) +{ + krb5_db_entry kdb; + osa_princ_ent_rec adb; + osa_adb_ret_t ret = 0; + long mask; + int i; + kadm5_server_handle_t handle = server_handle; + kadm5_principal_ent_rec entry_local, *entry_orig; + + CHECK_HANDLE(server_handle); + + /* + * In version 1, all the defined fields are always returned. + * entry is a pointer to a kadm5_principal_ent_t_v1 that should be + * filled with allocated memory. + */ + if (handle->api_version == KADM5_API_VERSION_1) { + mask = KADM5_PRINCIPAL_NORMAL_MASK; + entry_orig = entry; + entry = &entry_local; + } else { + mask = in_mask; + } + + memset((char *) entry, 0, sizeof(*entry)); + + if (principal == NULL) + return EINVAL; + + if ((ret = kdb_get_entry(handle, principal, &kdb, &adb))) + return ret; + + if ((mask & KADM5_POLICY) && + adb.policy && (adb.aux_attributes & KADM5_POLICY)) { + if ((entry->policy = (char *) malloc(strlen(adb.policy) + 1)) == NULL) { + ret = ENOMEM; + goto done; + } + strcpy(entry->policy, adb.policy); + } + + if (mask & KADM5_AUX_ATTRIBUTES) + entry->aux_attributes = adb.aux_attributes; + + if ((mask & KADM5_PRINCIPAL) && + (ret = krb5_copy_principal(handle->context, principal, + &entry->principal))) { + goto done; + } + + if (mask & KADM5_PRINC_EXPIRE_TIME) + entry->princ_expire_time = kdb.expiration; + + if ((mask & KADM5_LAST_PWD_CHANGE) && + (ret = krb5_dbe_lookup_last_pwd_change(handle->context, &kdb, + &(entry->last_pwd_change)))) { + goto done; + } + + if (mask & KADM5_PW_EXPIRATION) + entry->pw_expiration = kdb.pw_expiration; + if (mask & KADM5_MAX_LIFE) + entry->max_life = kdb.max_life; + + /* this is a little non-sensical because the function returns two */ + /* values that must be checked separately against the mask */ + if ((mask & KADM5_MOD_NAME) || (mask & KADM5_MOD_TIME)) { + if (ret = krb5_dbe_lookup_mod_princ_data(handle->context, &kdb, + &(entry->mod_date), + &(entry->mod_name))) { + goto done; + } + if (! (mask & KADM5_MOD_TIME)) + entry->mod_date = 0; + if (! (mask & KADM5_MOD_NAME)) { + krb5_free_principal(handle->context, entry->principal); + entry->principal = NULL; + } + } + + if (mask & KADM5_ATTRIBUTES) + entry->attributes = kdb.attributes; + + if (mask & KADM5_KVNO) + for (entry->kvno = 0, i=0; i<kdb.n_key_data; i++) + if (kdb.key_data[i].key_data_kvno > entry->kvno) + entry->kvno = kdb.key_data[i].key_data_kvno; + + if (handle->api_version == KADM5_API_VERSION_2) + entry->mkvno = 0; + else { + /* XXX I'll be damned if I know how to deal with this one --marc */ + entry->mkvno = 1; + } + + /* + * The new fields that only exist in version 2 start here + */ + if (handle->api_version == KADM5_API_VERSION_2) { + if (mask & KADM5_MAX_RLIFE) + entry->max_renewable_life = kdb.max_renewable_life; + if (mask & KADM5_LAST_SUCCESS) + entry->last_success = kdb.last_success; + if (mask & KADM5_LAST_FAILED) + entry->last_failed = kdb.last_failed; + if (mask & KADM5_FAIL_AUTH_COUNT) + entry->fail_auth_count = kdb.fail_auth_count; + if (mask & KADM5_TL_DATA) { + krb5_tl_data td, *tl, *tl2; + + entry->n_tl_data = kdb.n_tl_data; + entry->tl_data = NULL; + + tl = kdb.tl_data; + while (tl) { + if ((tl2 = dup_tl_data(tl)) == NULL) { + ret = ENOMEM; + goto done; + } + tl2->tl_data_next = entry->tl_data; + entry->tl_data = tl2; + + tl = tl->tl_data_next; + } + + if (kdb.e_length) { + td.tl_data_type = KRB5_TL_KADM5_E_DATA; + td.tl_data_length = kdb.e_length; + td.tl_data_contents = kdb.e_data; + + if ((tl = dup_tl_data(&td)) == NULL) { + ret = ENOMEM; + goto done; + } + tl->tl_data_next = entry->tl_data; + entry->tl_data = tl; + } + } + if (mask & KADM5_KEY_DATA) { + entry->n_key_data = kdb.n_key_data; + entry->key_data = (krb5_key_data *) + malloc(entry->n_key_data*sizeof(krb5_key_data)); + if (entry->key_data == NULL) { + ret = ENOMEM; + goto done; + } + for (i = 0; i < entry->n_key_data; i++) + if (ret = krb5_copy_key_data_contents(handle->context, + &kdb.key_data[i], + &entry->key_data[i])) + goto done; + } + } + + /* + * If KADM5_API_VERSION_1, we return an allocated structure, and + * we need to convert the new structure back into the format the + * caller is expecting. + */ + if (handle->api_version == KADM5_API_VERSION_1) { + kadm5_principal_ent_t_v1 newv1; + + newv1 = ((kadm5_principal_ent_t_v1) calloc(1, sizeof(*newv1))); + if (newv1 == NULL) { + ret = ENOMEM; + goto done; + } + + newv1->principal = entry->principal; + newv1->princ_expire_time = entry->princ_expire_time; + newv1->last_pwd_change = entry->last_pwd_change; + newv1->pw_expiration = entry->pw_expiration; + newv1->max_life = entry->max_life; + newv1->mod_name = entry->mod_name; + newv1->mod_date = entry->mod_date; + newv1->attributes = entry->attributes; + newv1->kvno = entry->kvno; + newv1->mkvno = entry->mkvno; + newv1->policy = entry->policy; + newv1->aux_attributes = entry->aux_attributes; + + *((kadm5_principal_ent_t_v1 *) entry_orig) = newv1; + } + + ret = KADM5_OK; + +done: + if (ret && entry->principal) + krb5_free_principal(handle->context, entry->principal); + kdb_free_entry(handle, &kdb, &adb); + + return ret; +} + +/* + * Function: check_pw_reuse + * + * Purpose: Check if a key appears in a list of keys, in order to + * enforce password history. + * + * Arguments: + * + * context (r) the krb5 context + * histkey_encblock (r) the encblock that hist_key_data is + * encrypted in + * n_new_key_data (r) length of new_key_data + * new_key_data (r) keys to check against + * pw_hist_data, encrypted in histkey_encblock + * n_pw_hist_data (r) length of pw_hist_data + * pw_hist_data (r) passwords to check new_key_data against + * + * Effects: + * For each new_key in new_key_data: + * decrypt new_key with the master_encblock + * for each password in pw_hist_data: + * for each hist_key in password: + * decrypt hist_key with histkey_encblock + * compare the new_key and hist_key + * + * Returns krb5 errors, KADM5_PASS_RESUSE if a key in + * new_key_data is the same as a key in pw_hist_data, or 0. + */ +static kadm5_ret_t +check_pw_reuse(krb5_context context, + krb5_encrypt_block *histkey_encblock, + int n_new_key_data, krb5_key_data *new_key_data, + int n_pw_hist_data, osa_pw_hist_ent *pw_hist_data) +{ + int x, y, z; + krb5_keyblock newkey, histkey; + krb5_error_code ret; + + for (x = 0; x < n_new_key_data; x++) { + if (ret = krb5_dbekd_decrypt_key_data(context, + &master_encblock, + &(new_key_data[x]), + &newkey, NULL)) + return(ret); + for (y = 0; y < n_pw_hist_data; y++) { + for (z = 0; z < pw_hist_data[y].n_key_data; z++) { + if (ret = + krb5_dbekd_decrypt_key_data(context, + histkey_encblock, + &pw_hist_data[y].key_data[z], + &histkey, NULL)) + return(ret); + + if ((newkey.length == histkey.length) && + (newkey.enctype == histkey.enctype) && + (memcmp(newkey.contents, histkey.contents, + histkey.length) == 0)) { + krb5_free_keyblock_contents(context, &histkey); + krb5_free_keyblock_contents(context, &newkey); + + return(KADM5_PASS_REUSE); + } + krb5_free_keyblock_contents(context, &histkey); + } + } + krb5_free_keyblock_contents(context, &newkey); + } + + return(0); +} + +/* + * Function: create_history_entry + * + * Purpose: Creates a password history entry from an array of + * key_data. + * + * Arguments: + * + * context (r) krb5_context to use + * n_key_data (r) number of elements in key_data + * key_data (r) keys to add to the history entry + * hist (w) history entry to fill in + * + * Effects: + * + * hist->key_data is allocated to store n_key_data key_datas. Each + * element of key_data is decrypted with master_encblock, re-encrypted + * in hist_encblock, and added to hist->key_data. hist->n_key_data is + * set to n_key_data. + */ +int create_history_entry(krb5_context context, int n_key_data, + krb5_key_data *key_data, osa_pw_hist_ent *hist) +{ + int i, ret; + krb5_keyblock key; + krb5_keysalt salt; + + hist->key_data = (krb5_key_data*)malloc(n_key_data*sizeof(krb5_key_data)); + if (hist->key_data == NULL) + return ENOMEM; + memset(hist->key_data, 0, n_key_data*sizeof(krb5_key_data)); + + for (i = 0; i < n_key_data; i++) { + if (ret = krb5_dbekd_decrypt_key_data(context, + &master_encblock, + &key_data[i], + &key, &salt)) + return ret; + if (ret = krb5_dbekd_encrypt_key_data(context, + &hist_encblock, + &key, &salt, + key_data[i].key_data_kvno, + &hist->key_data[i])) + return ret; + krb5_free_keyblock_contents(context, &key); + /* krb5_free_keysalt(context, &salt); */ + } + + hist->n_key_data = n_key_data; + return 0; +} + +int free_history_entry(krb5_context context, osa_pw_hist_ent *hist) +{ + int i; + + for (i = 0; i < hist->n_key_data; i++) + krb5_free_key_data_contents(context, &hist->key_data[i]); + free(hist->key_data); +} + +/* + * Function: add_to_history + * + * Purpose: Adds a password to a principal's password history. + * + * Arguments: + * + * context (r) krb5_context to use + * adb (r/w) admin principal entry to add keys to + * pol (r) adb's policy + * pw (r) keys for the password to add to adb's key history + * + * Effects: + * + * add_to_history adds a single password to adb's password history. + * pw contains n_key_data keys in its key_data, in storage should be + * allocated but not freed by the caller (XXX blech!). + * + * This function maintains adb->old_keys as a circular queue. It + * starts empty, and grows each time this function is called until it + * is pol->pw_history_num items long. adb->old_key_len holds the + * number of allocated entries in the array, and must therefore be [0, + * pol->pw_history_num). adb->old_key_next is the index into the + * array where the next element should be written, and must be [0, + * adb->old_key_len). + */ +static kadm5_ret_t add_to_history(krb5_context context, + osa_princ_ent_t adb, + kadm5_policy_ent_t pol, + osa_pw_hist_ent *pw) +{ + osa_pw_hist_ent hist, *histp; + int ret, i; + + /* A history of 1 means just check the current password */ + if (pol->pw_history_num == 1) + return 0; + + /* resize the adb->old_keys array if necessary */ + if (adb->old_key_len < pol->pw_history_num-1) { + adb->old_keys = (osa_pw_hist_ent *) + realloc(adb->old_keys, + (adb->old_key_len+1)*sizeof(osa_pw_hist_ent)); + if (adb->old_keys == NULL) + return(ENOMEM); + + memset(&adb->old_keys[adb->old_key_len],0,sizeof(osa_pw_hist_ent)); + adb->old_key_len++; + } + + /* free the old pw history entry if it contains data */ + histp = &adb->old_keys[adb->old_key_next]; + for (i = 0; i < histp->n_key_data; i++) + krb5_free_key_data_contents(context, &histp->key_data[i]); + + /* store the new entry */ + adb->old_keys[adb->old_key_next] = *pw; + + /* update the next pointer */ + if (++adb->old_key_next == pol->pw_history_num-1) + adb->old_key_next = 0; + + return(0); +} + +kadm5_ret_t +kadm5_chpass_principal(void *server_handle, + krb5_principal principal, char *password) +{ + krb5_int32 now; + kadm5_policy_ent_rec pol; + osa_princ_ent_rec adb; + krb5_db_entry kdb, kdb_save; + int ret, ret2, last_pwd, i, hist_added; + int have_pol = 0; + kadm5_server_handle_t handle = server_handle; + osa_pw_hist_ent hist; + + CHECK_HANDLE(server_handle); + + hist_added = 0; + memset(&hist, 0, sizeof(hist)); + + if (principal == NULL || password == NULL) + return EINVAL; + if ((krb5_principal_compare(handle->context, + principal, hist_princ)) == TRUE) + return KADM5_PROTECT_PRINCIPAL; + + if ((ret = kdb_get_entry(handle, principal, &kdb, &adb))) + return(ret); + + /* we are going to need the current keys after the new keys are set */ + if ((ret = kdb_get_entry(handle, principal, &kdb_save, NULL))) { + kdb_free_entry(handle, &kdb, &adb); + return(ret); + } + + if ((adb.aux_attributes & KADM5_POLICY)) { + if ((ret = kadm5_get_policy(handle->lhandle, adb.policy, &pol))) + goto done; + have_pol = 1; + } + + if ((ret = passwd_check(handle, password, adb.aux_attributes & + KADM5_POLICY, &pol, principal))) + goto done; + + if (ret = krb5_dbe_cpw(handle->context, &master_encblock, + handle->params.keysalts, + handle->params.num_keysalts, + password, 0 /* increment kvno */, &kdb)) + goto done; + + kdb.attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE; + + if (ret = krb5_timeofday(handle->context, &now)) + goto done; + + if ((adb.aux_attributes & KADM5_POLICY)) { + /* the policy was loaded before */ + + if (ret = krb5_dbe_lookup_last_pwd_change(handle->context, + &kdb, &last_pwd)) + goto done; + +#if 0 + /* + * The spec says this check is overridden if the caller has + * modify privilege. The admin server therefore makes this + * check itself (in chpass_principal_wrapper, misc.c). A + * local caller implicitly has all authorization bits. + */ + if ((now - last_pwd) < pol.pw_min_life && + !(kdb.attributes & KRB5_KDB_REQUIRES_PWCHANGE)) { + ret = KADM5_PASS_TOOSOON; + goto done; + } +#endif + + if (ret = create_history_entry(handle->context, + kdb_save.n_key_data, + kdb_save.key_data, &hist)) + goto done; + + if (ret = check_pw_reuse(handle->context, + &hist_encblock, + kdb.n_key_data, kdb.key_data, + 1, &hist)) + goto done; + + if (pol.pw_history_num > 1) { + if (adb.admin_history_kvno != hist_kvno) { + ret = KADM5_BAD_HIST_KEY; + goto done; + } + + if (ret = check_pw_reuse(handle->context, + &hist_encblock, + kdb.n_key_data, kdb.key_data, + adb.old_key_len, adb.old_keys)) + goto done; + + if (ret = add_to_history(handle->context, &adb, &pol, &hist)) + goto done; + hist_added = 1; + } + + if (pol.pw_max_life) + kdb.pw_expiration = now + pol.pw_max_life; + else + kdb.pw_expiration = 0; + } else { + kdb.pw_expiration = 0; + } + + if (ret = krb5_dbe_update_last_pwd_change(handle->context, &kdb, now)) + goto done; + + if ((ret = kdb_put_entry(handle, &kdb, &adb))) + goto done; + + ret = KADM5_OK; +done: + if (!hist_added && hist.key_data) + free_history_entry(handle->context, &hist); + kdb_free_entry(handle, &kdb, &adb); + kdb_free_entry(handle, &kdb_save, NULL); + krb5_dbe_free_contents(handle->context, &kdb); + + if (have_pol && (ret2 = kadm5_free_policy_ent(handle->lhandle, &pol)) + && !ret) + ret = ret2; + + return ret; +} + +kadm5_ret_t +kadm5_randkey_principal(void *server_handle, + krb5_principal principal, + krb5_keyblock **keyblocks, + int *n_keys) +{ + krb5_db_entry kdb; + osa_princ_ent_rec adb; + krb5_int32 now; + kadm5_policy_ent_rec pol; + krb5_key_data *key_data; + krb5_keyblock *keyblock; + int ret, last_pwd, have_pol = 0; + kadm5_server_handle_t handle = server_handle; + + if (keyblocks) + *keyblocks = NULL; + + CHECK_HANDLE(server_handle); + + if (principal == NULL) + return EINVAL; + if (hist_princ && /* this will be NULL when initializing the databse */ + ((krb5_principal_compare(handle->context, + principal, hist_princ)) == TRUE)) + return KADM5_PROTECT_PRINCIPAL; + + if ((ret = kdb_get_entry(handle, principal, &kdb, &adb))) + return(ret); + + if (ret = krb5_dbe_crk(handle->context, &master_encblock, + handle->params.keysalts, + handle->params.num_keysalts, + &kdb)) + goto done; + + kdb.attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE; + + if (ret = krb5_timeofday(handle->context, &now)) + goto done; + + if ((adb.aux_attributes & KADM5_POLICY)) { + if ((ret = kadm5_get_policy(handle->lhandle, adb.policy, + &pol)) != KADM5_OK) + goto done; + have_pol = 1; + + if (ret = krb5_dbe_lookup_last_pwd_change(handle->context, + &kdb, &last_pwd)) + goto done; + +#if 0 + /* + * The spec says this check is overridden if the caller has + * modify privilege. The admin server therefore makes this + * check itself (in chpass_principal_wrapper, misc.c). A + * local caller implicitly has all authorization bits. + */ + if((now - last_pwd) < pol.pw_min_life && + !(kdb.attributes & KRB5_KDB_REQUIRES_PWCHANGE)) { + ret = KADM5_PASS_TOOSOON; + goto done; + } +#endif + + if(pol.pw_history_num > 1) { + if(adb.admin_history_kvno != hist_kvno) { + ret = KADM5_BAD_HIST_KEY; + goto done; + } + + if (ret = check_pw_reuse(handle->context, + &hist_encblock, + kdb.n_key_data, kdb.key_data, + adb.old_key_len, adb.old_keys)) + goto done; + } + if (pol.pw_max_life) + kdb.pw_expiration = now + pol.pw_max_life; + else + kdb.pw_expiration = 0; + } else { + kdb.pw_expiration = 0; + } + + if (ret = krb5_dbe_update_last_pwd_change(handle->context, &kdb, now)) + goto done; + + if (keyblocks) { + if (handle->api_version == KADM5_API_VERSION_1) { + /* Version 1 clients will expect to see a DES_CRC enctype. */ + if (ret = krb5_dbe_find_enctype(handle->context, &kdb, + ENCTYPE_DES_CBC_CRC, + -1, -1, &key_data)) + goto done; + + if (ret = decrypt_key_data(handle->context, 1, key_data, + keyblocks, NULL)) + goto done; + } else { + ret = decrypt_key_data(handle->context, + kdb.n_key_data, kdb.key_data, + keyblocks, n_keys); + if (ret) + goto done; + } + } + + if ((ret = kdb_put_entry(handle, &kdb, &adb))) + goto done; + + ret = KADM5_OK; +done: + kdb_free_entry(handle, &kdb, &adb); + if (have_pol) + kadm5_free_policy_ent(handle->lhandle, &pol); + + return ret; +} + +/* + * Allocate an array of n_key_data krb5_keyblocks, fill in each + * element with the results of decrypting the nth key in key_data with + * master_encblock, and if n_keys is not NULL fill it in with the + * number of keys decrypted. + */ +static int decrypt_key_data(krb5_context context, + int n_key_data, krb5_key_data *key_data, + krb5_keyblock **keyblocks, int *n_keys) +{ + krb5_keyblock *keys; + int ret, i; + + keys = (krb5_keyblock *) malloc(n_key_data*sizeof(krb5_keyblock)); + if (keys == NULL) + return ENOMEM; + memset((char *) keys, 0, n_key_data*sizeof(krb5_keyblock)); + + for (i = 0; i < n_key_data; i++) { + if (ret = krb5_dbekd_decrypt_key_data(context, + &master_encblock, + &key_data[i], + &keys[i], NULL)) { + + memset((char *) keys, 0, n_key_data*sizeof(krb5_keyblock)); + free(keys); + return ret; + } + } + + *keyblocks = keys; + if (n_keys) + *n_keys = n_key_data; + + return 0; +} + +/* + * Function: kadm5_decrypt_key + * + * Purpose: Retrieves and decrypts a principal key. + * + * Arguments: + * + * server_handle (r) kadm5 handle + * entry (r) principal retrieved with kadm5_get_principal + * ktype (r) enctype to search for, or -1 to ignore + * stype (r) salt type to search for, or -1 to ignore + * kvno (r) kvno to search for, -1 for max, 0 for max + * only if it also matches ktype and stype + * keyblock (w) keyblock to fill in + * keysalt (w) keysalt to fill in, or NULL + * kvnop (w) kvno to fill in, or NULL + * + * Effects: Searches the key_data array of entry, which must have been + * retrived with kadm5_get_principal with the KADM5_KEY_DATA mask, to + * find a key with a specified enctype, salt type, and kvno in a + * principal entry. If not found, return ENOENT. Otherwise, decrypt + * it with the master key, and return the key in keyblock, the salt + * in salttype, and the key version number in kvno. + * + * If ktype or stype is -1, it is ignored for the search. If kvno is + * -1, ktype and stype are ignored and the key with the max kvno is + * returned. If kvno is 0, only the key with the max kvno is returned + * and only if it matches the ktype and stype; otherwise, ENOENT is + * returned. + */ +kadm5_ret_t kadm5_decrypt_key(void *server_handle, + kadm5_principal_ent_t entry, krb5_int32 + ktype, krb5_int32 stype, krb5_int32 + kvno, krb5_keyblock *keyblock, + krb5_keysalt *keysalt, int *kvnop) +{ + kadm5_server_handle_t handle = server_handle; + krb5_db_entry dbent; + krb5_key_data *key_data; + int ret; + + CHECK_HANDLE(server_handle); + + if (entry->n_key_data == 0 || entry->key_data == NULL) + return EINVAL; + + /* find_enctype only uses these two fields */ + dbent.n_key_data = entry->n_key_data; + dbent.key_data = entry->key_data; + if (ret = krb5_dbe_find_enctype(handle->context, &dbent, ktype, + stype, kvno, &key_data)) + return ret; + + if (ret = krb5_dbekd_decrypt_key_data(handle->context, + &master_encblock, key_data, + keyblock, keysalt)) + return ret; + + if (kvnop) + *kvnop = key_data->key_data_kvno; + + return KADM5_OK; +} |