/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* prof_init.c --- routines that manipulate the user-visible profile_t
* object.
*/
#include "prof_int.h"
#include <stdio.h>
#include <string.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <errno.h>
/* Create a vtable profile, possibly with a library handle. The new profile
* takes ownership of the handle refcount on success. */
static errcode_t
init_module(struct profile_vtable *vtable, void *cbdata,
prf_lib_handle_t handle, profile_t *ret_profile)
{
profile_t profile;
struct profile_vtable *vt_copy;
/* Check that the vtable's minor version is sane and that mandatory methods
* are implemented. */
if (vtable->minor_ver < 1 || !vtable->get_values || !vtable->free_values)
return EINVAL;
if (vtable->cleanup && !vtable->copy)
return EINVAL;
if (vtable->iterator_create &&
(!vtable->iterator || !vtable->iterator_free || !vtable->free_string))
return EINVAL;
profile = malloc(sizeof(*profile));
if (!profile)
return ENOMEM;
memset(profile, 0, sizeof(*profile));
vt_copy = malloc(sizeof(*vt_copy));
if (!vt_copy) {
free(profile);
return ENOMEM;
}
/* It's safe to just copy the caller's vtable for now. If the minor
* version is bumped, we'll need to copy individual fields. */
*vt_copy = *vtable;
profile->vt = vt_copy;
profile->cbdata = cbdata;
profile->lib_handle = handle;
profile->magic = PROF_MAGIC_PROFILE;
*ret_profile = profile;
return 0;
}
/* Parse modspec into the module path and residual string. */
static errcode_t
parse_modspec(const char *modspec, char **ret_path, char **ret_residual)
{
const char *p;
char *path, *fullpath, *residual;
errcode_t ret;
*ret_path = *ret_residual = NULL;
/* Find the separator, skipping a Windows drive letter if present. */
p = (*modspec != '\0' && modspec[1] == ':') ? modspec + 2 : modspec;
p = strchr(p, ':');
if (p == NULL)
return PROF_MODULE_SYNTAX;
/* Copy the path. */
path = malloc(p - modspec + 1);
if (path == NULL)
return ENOMEM;
memcpy(path, modspec, p - modspec);
path[p - modspec] = '\0';
/* Compose the path with LIBDIR if it's not absolute. */
ret = k5_path_join(LIBDIR, path, &fullpath);
free(path);
if (ret)
return ret;
residual = strdup(p + 1);
if (residual == NULL) {
free(fullpath);
return ENOMEM;
}
*ret_path = fullpath;
*ret_residual = residual;
return 0;
}
/* Load a dynamic profile module as specified by modspec and create a vtable
* profile for it in *ret_profile. */
static errcode_t
init_load_module(const char *modspec, profile_t *ret_profile)
{
char *modpath = NULL, *residual = NULL;
struct errinfo einfo = { 0 };
prf_lib_handle_t lib_handle = NULL;
struct plugin_file_handle *plhandle = NULL;
void *cbdata = NULL, (*fptr)();
int have_lock = 0, have_cbdata = 0;
struct profile_vtable vtable = { 1 }; /* Set minor_ver to 1, rest null. */
errcode_t err;
profile_module_init_fn initfn;
err = parse_modspec(modspec, &modpath, &residual);
if (err)
goto cleanup;
/* Allocate a reference-counted library handle container. */
lib_handle = malloc(sizeof(*lib_handle));
if (lib_handle == NULL)
goto cleanup;
err = k5_mutex_init(&lib_handle->lock);
if (err)
goto cleanup;
have_lock = 1;
/* Open the module and get its initializer. */
err = krb5int_open_plugin(modpath, &plhandle, &einfo);
if (err)
goto cleanup;
err = krb5int_get_plugin_func(plhandle, "profile_module_init", &fptr,
&einfo);
if (err == ENOENT)
err = PROF_MODULE_INVALID;
if (err)
goto cleanup;
/* Get the profile vtable and callback data pointer. */
initfn = (profile_module_init_fn)fptr;
err = (*initfn)(residual, &vtable, &cbdata);
if (err)
goto cleanup;
have_cbdata = 1;
/* Create a vtable profile with the information obtained. */
lib_handle->plugin_handle = plhandle;
lib_handle->refcount = 1;
err = init_module(&vtable, cbdata, lib_handle, ret_profile);
cleanup:
free(modpath);
free(residual);
k5_clear_error(&einfo);
if (err) {
if (have_cbdata && vtable.cleanup)
vtable.cleanup(cbdata);
if (have_lock)
k5_mutex_destroy(&lib_handle->lock);
free(lib_handle);
if (plhandle)
krb5int_close_plugin(plhandle);
}
return err;
}
errcode_t KRB5_CALLCONV
profile_init_flags(const_profile_filespec_t *files, int flags,
profile_t *ret_profile)
{
const_profile_filespec_t *fs;
profile_t profile;
prf_file_t new_file, last = 0;
errcode_t retval = 0, access_retval = 0;
char *modspec = NULL, **modspec_arg;
profile = malloc(sizeof(struct _profile_t));
if (!profile)
return ENOMEM;
memset(profile, 0, sizeof(struct _profile_t));
profile->magic = PROF_MAGIC_PROFILE;
/*
* If the filenames list is not specified or empty, return an empty
* profile.
*/
if ( files && !PROFILE_LAST_FILESPEC(*files) ) {
for (fs = files; !PROFILE_LAST_FILESPEC(*fs); fs++) {
/* Allow a module declaration if it is permitted by flags and this
* is the first file parsed. */
modspec_arg = ((flags & PROFILE_INIT_ALLOW_MODULE) && !last) ?
&modspec : NULL;
retval = profile_open_file(*fs, &new_file, modspec_arg);
if (retval == PROF_MODULE && modspec) {
/* Stop parsing files and load a dynamic module instead. */
free(profile);
retval = init_load_module(modspec, ret_profile);
free(modspec);
return retval;
}
/* if this file is missing, skip to the next */
if (retval == ENOENT) {
continue;
}
/* If we can't read this file, remember it but keep going. */
if (retval == EACCES || retval == EPERM) {
access_retval = retval;
continue;
}
if (retval) {
profile_release(profile);
return retval;
}
if (last)
last->next = new_file;
else
profile->first_file = new_file;
last = new_file;
}
/*
* If last is still null after the loop, then all the files were
* missing or unreadable, so return the appropriate error.
*/
if (!last) {
profile_release(profile);
return access_retval ? access_retval : ENOENT;
}
}
*ret_profile = profile;
return 0;
}
errcode_t KRB5_CALLCONV
profile_init(const_profile_filespec_t *files, profile_t *ret_profile)
{
return profile_init_flags(files, 0, ret_profile);
}
errcode_t KRB5_CALLCONV
profile_init_vtable(struct profile_vtable *vtable, void *cbdata,
profile_t *ret_profile)
{
return init_module(vtable, cbdata, NULL, ret_profile);
}
/* Copy a vtable profile. */
static errcode_t
copy_vtable_profile(profile_t profile, profile_t *ret_new_profile)
{
errcode_t err;
void *cbdata;
profile_t new_profile;
*ret_new_profile = NULL;
if (profile->vt->copy) {
/* Make a copy of profile's cbdata for the new profile. */
err = profile->vt->copy(profile->cbdata, &cbdata);
if (err)
return err;
err = init_module(profile->vt, cbdata, profile->lib_handle,
&new_profile);
if (err && profile->vt->cleanup)
profile->vt->cleanup(cbdata);
} else {
/* Use the same cbdata as the old profile. */
err = init_module(profile->vt, profile->cbdata, profile->lib_handle,
&new_profile);
}
if (err)
return err;
/* Increment the refcount on the library handle if there is one. */
if (profile->lib_handle) {
k5_mutex_lock(&profile->lib_handle->lock);
profile->lib_handle->refcount++;
k5_mutex_unlock(&profile->lib_handle->lock);
}
*ret_new_profile = new_profile;
return 0;
}
#define COUNT_LINKED_LIST(COUNT, PTYPE, START, FIELD) \
{ \
size_t cll_counter = 0; \
PTYPE cll_ptr = (START); \
while (cll_ptr != NULL) { \
cll_counter++; \
cll_ptr = cll_ptr->FIELD; \
} \
(COUNT) = cll_counter; \
}
errcode_t KRB5_CALLCONV
profile_copy(profile_t old_profile, profile_t *new_profile)
{
size_t size, i;
const_profile_filespec_t *files;
prf_file_t file;
errcode_t err;
if (old_profile->vt)
return copy_vtable_profile(old_profile, new_profile);
/* The fields we care about are read-only after creation, so
no locking is needed. */
COUNT_LINKED_LIST (size, prf_file_t, old_profile->first_file, next);
files = malloc ((size+1) * sizeof(*files));
if (files == NULL)
return ENOMEM;
for (i = 0, file = old_profile->first_file; i < size; i++, file = file->next)
files[i] = file->data->filespec;
files[size] = NULL;
err = profile_init (files, new_profile);
free (files);
return err;
}
errcode_t KRB5_CALLCONV
profile_init_path(const_profile_filespec_list_t filepath,
profile_t *ret_profile)
{
unsigned int n_entries;
int i;
unsigned int ent_len;
const char *s, *t;
profile_filespec_t *filenames;
errcode_t retval;
/* count the distinct filename components */
for(s = filepath, n_entries = 1; *s; s++) {
if (*s == ':')
n_entries++;
}
/* the array is NULL terminated */
filenames = (profile_filespec_t*) malloc((n_entries+1) * sizeof(char*));
if (filenames == 0)
return ENOMEM;
/* measure, copy, and skip each one */
for(s = filepath, i=0; (t = strchr(s, ':')) || (t=s+strlen(s)); s=t+1, i++) {
ent_len = (unsigned int) (t-s);
filenames[i] = (char*) malloc(ent_len + 1);
if (filenames[i] == 0) {
/* if malloc fails, free the ones that worked */
while(--i >= 0) free(filenames[i]);
free(filenames);
return ENOMEM;
}
strncpy(filenames[i], s, ent_len);
filenames[i][ent_len] = 0;
if (*t == 0) {
i++;
break;
}
}
/* cap the array */
filenames[i] = 0;
retval = profile_init_flags((const_profile_filespec_t *) filenames, 0,
ret_profile);
/* count back down and free the entries */
while(--i >= 0) free(filenames[i]);
free(filenames);
return retval;
}
errcode_t KRB5_CALLCONV
profile_is_writable(profile_t profile, int *writable)
{
if (!profile || profile->magic != PROF_MAGIC_PROFILE)
return PROF_MAGIC_PROFILE;
if (!writable)
return EINVAL;
*writable = 0;
if (profile->vt) {
if (profile->vt->writable)
return profile->vt->writable(profile->cbdata, writable);
else
return 0;
}
if (profile->first_file)
*writable = profile_file_is_writable(profile->first_file);
return 0;
}
errcode_t KRB5_CALLCONV
profile_is_modified(profile_t profile, int *modified)
{
if (!profile || profile->magic != PROF_MAGIC_PROFILE)
return PROF_MAGIC_PROFILE;
if (!modified)
return EINVAL;
*modified = 0;
if (profile->vt) {
if (profile->vt->modified)
return profile->vt->modified(profile->cbdata, modified);
else
return 0;
}
if (profile->first_file)
*modified = (profile->first_file->data->flags & PROFILE_FILE_DIRTY);
return 0;
}
errcode_t KRB5_CALLCONV
profile_flush(profile_t profile)
{
if (!profile || profile->magic != PROF_MAGIC_PROFILE)
return PROF_MAGIC_PROFILE;
if (profile->vt) {
if (profile->vt->flush)
return profile->vt->flush(profile->cbdata);
return 0;
}
if (profile->first_file)
return profile_flush_file(profile->first_file);
return 0;
}
errcode_t KRB5_CALLCONV
profile_flush_to_file(profile_t profile, const_profile_filespec_t outfile)
{
if (!profile || profile->magic != PROF_MAGIC_PROFILE)
return PROF_MAGIC_PROFILE;
if (profile->vt)
return PROF_UNSUPPORTED;
if (profile->first_file)
return profile_flush_file_to_file(profile->first_file,
outfile);
return 0;
}
errcode_t KRB5_CALLCONV
profile_flush_to_buffer(profile_t profile, char **buf)
{
if (profile->vt)
return PROF_UNSUPPORTED;
return profile_flush_file_data_to_buffer(profile->first_file->data, buf);
}
void KRB5_CALLCONV
profile_free_buffer(profile_t profile, char *buf)
{
free(buf);
}
void KRB5_CALLCONV
profile_abandon(profile_t profile)
{
prf_file_t p, next;
if (!profile || profile->magic != PROF_MAGIC_PROFILE)
return;
if (profile->vt) {
if (profile->vt->cleanup)
profile->vt->cleanup(profile->cbdata);
if (profile->lib_handle) {
/* Decrement the refcount on the handle and maybe free it. */
k5_mutex_lock(&profile->lib_handle->lock);
if (--profile->lib_handle->refcount == 0) {
krb5int_close_plugin(profile->lib_handle->plugin_handle);
k5_mutex_unlock(&profile->lib_handle->lock);
k5_mutex_destroy(&profile->lib_handle->lock);
free(profile->lib_handle);
} else
k5_mutex_unlock(&profile->lib_handle->lock);
}
free(profile->vt);
} else {
for (p = profile->first_file; p; p = next) {
next = p->next;
profile_free_file(p);
}
}
profile->magic = 0;
free(profile);
}
void KRB5_CALLCONV
profile_release(profile_t profile)
{
prf_file_t p, next;
if (!profile || profile->magic != PROF_MAGIC_PROFILE)
return;
if (profile->vt) {
/* Flush the profile and then delegate to profile_abandon. */
if (profile->vt->flush)
profile->vt->flush(profile->cbdata);
profile_abandon(profile);
return;
} else {
for (p = profile->first_file; p; p = next) {
next = p->next;
profile_close_file(p);
}
}
profile->magic = 0;
free(profile);
}
/*
* Here begins the profile serialization functions.
*/
errcode_t profile_ser_size(const char *unused, profile_t profile,
size_t *sizep)
{
size_t required;
prf_file_t pfp;
required = 3*sizeof(int32_t);
for (pfp = profile->first_file; pfp; pfp = pfp->next) {
required += sizeof(int32_t);
required += strlen(pfp->data->filespec);
}
*sizep += required;
return 0;
}
static void pack_int32(int32_t oval, unsigned char **bufpp, size_t *remainp)
{
store_32_be(oval, *bufpp);
*bufpp += sizeof(int32_t);
*remainp -= sizeof(int32_t);
}
errcode_t profile_ser_externalize(const char *unused, profile_t profile,
unsigned char **bufpp, size_t *remainp)
{
errcode_t retval;
size_t required;
unsigned char *bp;
size_t remain;
prf_file_t pfp;
int32_t fcount, slen;
required = 0;
bp = *bufpp;
remain = *remainp;
retval = EINVAL;
if (profile) {
retval = ENOMEM;
(void) profile_ser_size(unused, profile, &required);
if (required <= remain) {
fcount = 0;
for (pfp = profile->first_file; pfp; pfp = pfp->next)
fcount++;
pack_int32(PROF_MAGIC_PROFILE, &bp, &remain);
pack_int32(fcount, &bp, &remain);
for (pfp = profile->first_file; pfp; pfp = pfp->next) {
slen = (int32_t) strlen(pfp->data->filespec);
pack_int32(slen, &bp, &remain);
if (slen) {
memcpy(bp, pfp->data->filespec, (size_t) slen);
bp += slen;
remain -= (size_t) slen;
}
}
pack_int32(PROF_MAGIC_PROFILE, &bp, &remain);
retval = 0;
*bufpp = bp;
*remainp = remain;
}
}
return(retval);
}
static int unpack_int32(int32_t *intp, unsigned char **bufpp,
size_t *remainp)
{
if (*remainp >= sizeof(int32_t)) {
*intp = load_32_be(*bufpp);
*bufpp += sizeof(int32_t);
*remainp -= sizeof(int32_t);
return 0;
}
else
return 1;
}
errcode_t profile_ser_internalize(const char *unused, profile_t *profilep,
unsigned char **bufpp, size_t *remainp)
{
errcode_t retval;
unsigned char *bp;
size_t remain;
int i;
int32_t fcount, tmp;
profile_filespec_t *flist = 0;
bp = *bufpp;
remain = *remainp;
fcount = 0;
if (remain >= 12)
(void) unpack_int32(&tmp, &bp, &remain);
else
tmp = 0;
if (tmp != PROF_MAGIC_PROFILE) {
retval = EINVAL;
goto cleanup;
}
(void) unpack_int32(&fcount, &bp, &remain);
retval = ENOMEM;
flist = (profile_filespec_t *) malloc(sizeof(profile_filespec_t) * (size_t) (fcount + 1));
if (!flist)
goto cleanup;
memset(flist, 0, sizeof(char *) * (size_t) (fcount+1));
for (i=0; i<fcount; i++) {
if (!unpack_int32(&tmp, &bp, &remain)) {
flist[i] = (char *) malloc((size_t) (tmp+1));
if (!flist[i])
goto cleanup;
memcpy(flist[i], bp, (size_t) tmp);
flist[i][tmp] = '\0';
bp += tmp;
remain -= (size_t) tmp;
}
}
if (unpack_int32(&tmp, &bp, &remain) ||
(tmp != PROF_MAGIC_PROFILE)) {
retval = EINVAL;
goto cleanup;
}
if ((retval = profile_init((const_profile_filespec_t *) flist,
profilep)))
goto cleanup;
*bufpp = bp;
*remainp = remain;
cleanup:
if (flist) {
for (i=0; i<fcount; i++) {
if (flist[i])
free(flist[i]);
}
free(flist);
}
return(retval);
}