From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- third_party/heimdal/lib/base/Makefile.am | 97 ++ third_party/heimdal/lib/base/NTMakefile | 134 ++ third_party/heimdal/lib/base/array.c | 478 +++++++ third_party/heimdal/lib/base/baselocl.h | 98 ++ third_party/heimdal/lib/base/bool.c | 59 + third_party/heimdal/lib/base/bsearch.c | 888 ++++++++++++ third_party/heimdal/lib/base/common_plugin.h | 106 ++ third_party/heimdal/lib/base/config_file.c | 1472 +++++++++++++++++++ third_party/heimdal/lib/base/config_reg.c | 658 +++++++++ third_party/heimdal/lib/base/context.c | 394 ++++++ third_party/heimdal/lib/base/data.c | 166 +++ third_party/heimdal/lib/base/db.c | 1724 +++++++++++++++++++++++ third_party/heimdal/lib/base/dict.c | 305 ++++ third_party/heimdal/lib/base/dll.c | 325 +++++ third_party/heimdal/lib/base/error.c | 181 +++ third_party/heimdal/lib/base/error_string.c | 177 +++ third_party/heimdal/lib/base/expand_path.c | 725 ++++++++++ third_party/heimdal/lib/base/heim_err.et | 57 + third_party/heimdal/lib/base/heimbase-atomics.h | 386 +++++ third_party/heimdal/lib/base/heimbase-svc.h | 84 ++ third_party/heimdal/lib/base/heimbase.c | 1104 +++++++++++++++ third_party/heimdal/lib/base/heimbase.h | 374 +++++ third_party/heimdal/lib/base/heimbasepriv.h | 93 ++ third_party/heimdal/lib/base/json.c | 1471 +++++++++++++++++++ third_party/heimdal/lib/base/log.c | 1079 ++++++++++++++ third_party/heimdal/lib/base/null.c | 53 + third_party/heimdal/lib/base/number.c | 136 ++ third_party/heimdal/lib/base/plugin.c | 785 +++++++++++ third_party/heimdal/lib/base/roken_rename.h | 61 + third_party/heimdal/lib/base/string.c | 261 ++++ third_party/heimdal/lib/base/test_base.c | 1380 ++++++++++++++++++ third_party/heimdal/lib/base/version-script.map | 211 +++ third_party/heimdal/lib/base/warn.c | 169 +++ 33 files changed, 15691 insertions(+) create mode 100644 third_party/heimdal/lib/base/Makefile.am create mode 100644 third_party/heimdal/lib/base/NTMakefile create mode 100644 third_party/heimdal/lib/base/array.c create mode 100644 third_party/heimdal/lib/base/baselocl.h create mode 100644 third_party/heimdal/lib/base/bool.c create mode 100644 third_party/heimdal/lib/base/bsearch.c create mode 100644 third_party/heimdal/lib/base/common_plugin.h create mode 100644 third_party/heimdal/lib/base/config_file.c create mode 100644 third_party/heimdal/lib/base/config_reg.c create mode 100644 third_party/heimdal/lib/base/context.c create mode 100644 third_party/heimdal/lib/base/data.c create mode 100644 third_party/heimdal/lib/base/db.c create mode 100644 third_party/heimdal/lib/base/dict.c create mode 100644 third_party/heimdal/lib/base/dll.c create mode 100644 third_party/heimdal/lib/base/error.c create mode 100644 third_party/heimdal/lib/base/error_string.c create mode 100644 third_party/heimdal/lib/base/expand_path.c create mode 100644 third_party/heimdal/lib/base/heim_err.et create mode 100644 third_party/heimdal/lib/base/heimbase-atomics.h create mode 100644 third_party/heimdal/lib/base/heimbase-svc.h create mode 100644 third_party/heimdal/lib/base/heimbase.c create mode 100644 third_party/heimdal/lib/base/heimbase.h create mode 100644 third_party/heimdal/lib/base/heimbasepriv.h create mode 100644 third_party/heimdal/lib/base/json.c create mode 100644 third_party/heimdal/lib/base/log.c create mode 100644 third_party/heimdal/lib/base/null.c create mode 100644 third_party/heimdal/lib/base/number.c create mode 100644 third_party/heimdal/lib/base/plugin.c create mode 100644 third_party/heimdal/lib/base/roken_rename.h create mode 100644 third_party/heimdal/lib/base/string.c create mode 100644 third_party/heimdal/lib/base/test_base.c create mode 100644 third_party/heimdal/lib/base/version-script.map create mode 100644 third_party/heimdal/lib/base/warn.c (limited to 'third_party/heimdal/lib/base') diff --git a/third_party/heimdal/lib/base/Makefile.am b/third_party/heimdal/lib/base/Makefile.am new file mode 100644 index 0000000..606e00a --- /dev/null +++ b/third_party/heimdal/lib/base/Makefile.am @@ -0,0 +1,97 @@ + +include $(top_srcdir)/Makefile.am.common + +if do_roken_rename +ES = base64.c +endif + +IMPLEMENT_TLS= +if MAINTAINER_MODE +IMPLEMENT_TLS += dll.c +AM_CPPFLAGS += -DHEIM_BASE_MAINTAINER +endif + +AM_CPPFLAGS += $(ROKEN_RENAME) -I../com_err -I$(srcdir)/../com_err + +lib_LTLIBRARIES = libheimbase.la +check_PROGRAMS = test_base + +test_base_CFLAGS = -Wno-string-concatenation + +libheimbase_la_LDFLAGS = -version-info 1:0:0 + +if FRAMEWORK_COREFOUNDATION +libheimbase_la_LDFLAGS += -framework CoreFoundation +endif + +TESTS = test_base + +if versionscript +libheimbase_la_LDFLAGS += $(LDFLAGS_VERSION_SCRIPT)$(srcdir)/version-script.map +endif + +libheimbase_la_LIBADD = $(PTHREAD_LIBADD) $(LIB_dlopen) $(LIB_com_err) + +include_HEADERS = heimbase.h common_plugin.h heimbase-atomics.h heimbase-svc.h + +ERR_FILES = heim_err.c + +dist_libheimbase_la_SOURCES = \ + array.c \ + baselocl.h \ + bsearch.c \ + bool.c \ + common_plugin.h \ + config_file.c \ + context.c \ + data.c \ + db.c \ + dict.c \ + $(IMPLEMENT_TLS) \ + error.c \ + error_string.c \ + expand_path.c \ + heimbase.c \ + heimbasepriv.h \ + json.c \ + log.c \ + null.c \ + number.c \ + plugin.c \ + roken_rename.h \ + string.c \ + warn.c + +nodist_libheimbase_la_SOURCES = $(ES) $(ERR_FILES) + +$(libheimbase_la_OBJECTS): $(srcdir)/heimbase-protos.h heim_err.h + +$(srcdir)/heimbase-protos.h: $(dist_libheimbase_la_SOURCES) + cd $(srcdir) && \ + perl ../../cf/make-proto.pl -q -P comment -o heimbase-protos.h $(dist_libheimbase_la_SOURCES) || \ + rm -f heimbase-protos.h + +$(srcdir)/heimbase-private.h: $(dist_libheimbase_la_SOURCES) + cd $(srcdir) && \ + perl ../../cf/make-proto.pl -q -P comment -p heimbase-private.h $(dist_libheimbase_la_SOURCES) || \ + rm -f heimbase-private.h + +# install these? + +libheimbase_la_DEPENDENCIES = version-script.map + +test_base_LDADD = libheimbase.la $(LIB_roken) + +CLEANFILES = base64.c test_db.json heim_err.c heim_err.h + +EXTRA_DIST = NTMakefile version-script.map config_reg.c heim_err.et + +dist_include_HEADERS = heimbase-protos.h + +nodist_include_HEADERS = heim_err.h + +heim_err.h: heim_err.et + +base64.c: + rm -f base64.c + $(LN_S) $(srcdir)/../roken/base64.c . diff --git a/third_party/heimdal/lib/base/NTMakefile b/third_party/heimdal/lib/base/NTMakefile new file mode 100644 index 0000000..49cd8b8 --- /dev/null +++ b/third_party/heimdal/lib/base/NTMakefile @@ -0,0 +1,134 @@ +######################################################################## +# +# Copyright (c) 2010, Secure Endpoints Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +RELDIR=lib\base + +intcflags=-I$(SRCDIR) -I$(OBJ) + +!include ../../windows/NTMakefile.w32 + +INCFILES= \ + $(INCDIR)\heimbase.h \ + $(INCDIR)\heimbase-protos.h \ + $(INCDIR)\heimbase-atomics.h \ + $(INCDIR)\heimbase-svc.h \ + $(INCDIR)\heim_err.h \ + $(INCDIR)\common_plugin.h + +test_binaries = $(OBJ)\test_base.exe + +libheimbase_SOURCES = \ + array.c \ + bool.c \ + bsearch.c \ + config_file.c \ + config_reg.c \ + context.c \ + data.c \ + db.c \ + dict.c \ + dll.c \ + error.c \ + error_string.c \ + expand_path.c \ + heimbase.c \ + json.c \ + log.c \ + null.c \ + number.c \ + plugin.c \ + string.c \ + warn.c + +libheimbase_OBJS = \ + $(OBJ)\array.obj \ + $(OBJ)\bool.obj \ + $(OBJ)\bsearch.obj \ + $(OBJ)\config_file.obj \ + $(OBJ)\config_reg.obj \ + $(OBJ)\context.obj \ + $(OBJ)\data.obj \ + $(OBJ)\db.obj \ + $(OBJ)\dict.obj \ + $(OBJ)\dll.obj \ + $(OBJ)\error.obj \ + $(OBJ)\error_string.obj \ + $(OBJ)\expand_path.obj \ + $(OBJ)\heimbase.obj \ + $(OBJ)\json.obj \ + $(OBJ)\log.obj \ + $(OBJ)\null.obj \ + $(OBJ)\number.obj \ + $(OBJ)\plugin.obj \ + $(OBJ)\string.obj \ + $(OBJ)\warn.obj + +libheimbase_gen_OBJS= $(OBJ)\heim_err.obj + +$(LIBHEIMBASE): $(libheimbase_OBJS) $(libheimbase_gen_OBJS) + $(LIBCON_C) -OUT:$@ $(LIBROKEN) $(LIBCOMERR) $(PTHREAD_LIB) Secur32.lib Shell32.lib Advapi32.lib Shlwapi.lib @<< +$(libheimbase_OBJS: = +) +$(libheimbase_gen_OBJS: = +) +<< + +$(OBJ)\heimbase-protos.h: $(libheimbase_SOURCES) + $(PERL) ..\..\cf\make-proto.pl -E KRB5_LIB -q -P remove -o $(OBJ)\heimbase-protos.h $(libheimbase_SOURCES) || $(RM) -f $(OBJ)\heimbase-protos.h + +$(OBJ)\heimbase-private.h: $(libheimbase_SOURCES) + $(PERL) ..\..\cf\make-proto.pl -q -P remove -p $(OBJ)\heimbase-private.h $(libheimbase_SOURCES) || $(RM) -f $(OBJ)\heimbase-private.h + +$(OBJ)\heim_err.c $(OBJ)\heim_err.h: heim_err.et + cd $(OBJ) + $(BINDIR)\compile_et.exe $(SRCDIR)\heim_err.et + cd $(SRCDIR) + +test:: test-binaries test-run + +test-run: + cd $(OBJ) + -test_base.exe + cd $(SRCDIR) + +all:: $(INCFILES) $(LIBHEIMBASE) + +clean:: + -$(RM) $(INCFILES) + +test-binaries: $(test_binaries) + +$(test_binaries): $$(@R).obj $(LIBHEIMBASE) $(LIBVERS) $(LIBROKEN) + $(EXECONLINK) + $(EXEPREP_NODIST) + +$(test_binaries:.exe=.obj): $$(@B).c + $(C2OBJ_C) -Fo$@ -Fd$(@D)\ $** -DBlah diff --git a/third_party/heimdal/lib/base/array.c b/third_party/heimdal/lib/base/array.c new file mode 100644 index 0000000..994fa7d --- /dev/null +++ b/third_party/heimdal/lib/base/array.c @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" + +/* + * + */ + +struct heim_array_data { + size_t len; + heim_object_t *val; + size_t allocated_len; + heim_object_t *allocated; +}; + +static void HEIM_CALLCONV +array_dealloc(heim_object_t ptr) +{ + heim_array_t array = ptr; + size_t n; + for (n = 0; n < array->len; n++) + heim_release(array->val[n]); + free(array->allocated); +} + +struct heim_type_data array_object = { + HEIM_TID_ARRAY, + "array-object", + NULL, + array_dealloc, + NULL, + NULL, + NULL, + NULL +}; + +/** + * Allocate an array + * + * @return A new allocated array, free with heim_release() + */ + +heim_array_t +heim_array_create(void) +{ + heim_array_t array; + + array = _heim_alloc_object(&array_object, sizeof(*array)); + if (array == NULL) + return NULL; + + array->allocated = NULL; + array->allocated_len = 0; + array->val = NULL; + array->len = 0; + + return array; +} + +/** + * Get type id of an dict + * + * @return the type id + */ + +heim_tid_t +heim_array_get_type_id(void) +{ + return HEIM_TID_ARRAY; +} + +/** + * Append object to array + * + * @param array array to add too + * @param object the object to add + * + * @return zero if added, errno otherwise + */ + +int +heim_array_append_value(heim_array_t array, heim_object_t object) +{ + heim_object_t *ptr; + size_t leading = array->val - array->allocated; /* unused leading slots */ + size_t trailing = array->allocated_len - array->len - leading; + size_t new_len; + + if (trailing > 0) { + /* We have pre-allocated space; use it */ + array->val[array->len++] = heim_retain(object); + return 0; + } + + if (leading > (array->len + 1)) { + /* + * We must have appending to, and deleting at index 0 from this + * array a lot; don't want to grow forever! + */ + (void) memmove(&array->allocated[0], &array->val[0], + array->len * sizeof(array->val[0])); + array->val = array->allocated; + + /* We have pre-allocated space; use it */ + array->val[array->len++] = heim_retain(object); + return 0; + } + + /* Pre-allocate extra .5 times number of used slots */ + new_len = leading + array->len + 1 + (array->len >> 1); + ptr = realloc(array->allocated, new_len * sizeof(array->val[0])); + if (ptr == NULL) + return ENOMEM; + array->allocated = ptr; + array->allocated_len = new_len; + array->val = &ptr[leading]; + array->val[array->len++] = heim_retain(object); + + return 0; +} + +/* + * Internal function to insert at index 0, taking care to optimize the + * case where we're always inserting at index 0, particularly the case + * where we insert at index 0 and delete from the right end. + */ +static int +heim_array_prepend_value(heim_array_t array, heim_object_t object) +{ + heim_object_t *ptr; + size_t leading = array->val - array->allocated; /* unused leading slots */ + size_t trailing = array->allocated_len - array->len - leading; + size_t new_len; + + if (leading > 0) { + /* We have pre-allocated space; use it */ + array->val--; + array->val[0] = heim_retain(object); + array->len++; + return 0; + } + if (trailing > (array->len + 1)) { + /* + * We must have prepending to, and deleting at index + * array->len - 1 from this array a lot; don't want to grow + * forever! + */ + (void) memmove(&array->allocated[array->len], &array->val[0], + array->len * sizeof(array->val[0])); + array->val = &array->allocated[array->len]; + + /* We have pre-allocated space; use it */ + array->val--; + array->val[0] = heim_retain(object); + array->len++; + return 0; + } + /* Pre-allocate extra .5 times number of used slots */ + new_len = array->len + 1 + trailing + (array->len >> 1); + ptr = realloc(array->allocated, new_len * sizeof(array->val[0])); + if (ptr == NULL) + return ENOMEM; + (void) memmove(&ptr[1], &ptr[0], array->len * sizeof (array->val[0])); + array->allocated = ptr; + array->allocated_len = new_len; + array->val = &ptr[0]; + array->val[0] = heim_retain(object); + array->len++; + + return 0; +} + +/** + * Insert an object at a given index in an array + * + * @param array array to add too + * @param idx index where to add element (-1 == append, -2 next to last, ...) + * @param object the object to add + * + * @return zero if added, errno otherwise + */ + +int +heim_array_insert_value(heim_array_t array, size_t idx, heim_object_t object) +{ + int ret; + + if (idx == 0) + return heim_array_prepend_value(array, object); + else if (idx > array->len) + heim_abort("index too large"); + + /* + * We cheat: append this element then rotate elements around so we + * have this new element at the desired location, unless we're truly + * appending the new element. This means reusing array growth in + * heim_array_append_value() instead of duplicating that here. + */ + ret = heim_array_append_value(array, object); + if (ret != 0 || idx == (array->len - 1)) + return ret; + /* + * Shift to the right by one all the elements after idx, then set + * [idx] to the new object. + */ + (void) memmove(&array->val[idx + 1], &array->val[idx], + (array->len - idx - 1) * sizeof(array->val[0])); + array->val[idx] = heim_retain(object); + + return 0; +} + +/** + * Iterate over all objects in array + * + * @param array array to iterate over + * @param ctx context passed to fn + * @param fn function to call on each object + */ + +void +heim_array_iterate_f(heim_array_t array, void *ctx, heim_array_iterator_f_t fn) +{ + size_t n; + int stop = 0; + for (n = 0; n < array->len; n++) { + fn(array->val[n], ctx, &stop); + if (stop) + return; + } +} + +#ifdef __BLOCKS__ +/** + * Iterate over all objects in array + * + * @param array array to iterate over + * @param fn block to call on each object + */ + +void +heim_array_iterate(heim_array_t array, void (^fn)(heim_object_t, int *)) +{ + size_t n; + int stop = 0; + for (n = 0; n < array->len; n++) { + fn(array->val[n], &stop); + if (stop) + return; + } +} +#endif + +/** + * Iterate over all objects in array, backwards + * + * @param array array to iterate over + * @param ctx context passed to fn + * @param fn function to call on each object + */ + +void +heim_array_iterate_reverse_f(heim_array_t array, void *ctx, heim_array_iterator_f_t fn) +{ + size_t n; + int stop = 0; + + for (n = array->len; n > 0; n--) { + fn(array->val[n - 1], ctx, &stop); + if (stop) + return; + } +} + +#ifdef __BLOCKS__ +/** + * Iterate over all objects in array, backwards + * + * @param array array to iterate over + * @param fn block to call on each object + */ + +void +heim_array_iterate_reverse(heim_array_t array, void (^fn)(heim_object_t, int *)) +{ + size_t n; + int stop = 0; + for (n = array->len; n > 0; n--) { + fn(array->val[n - 1], &stop); + if (stop) + return; + } +} +#endif + +/** + * Get length of array + * + * @param array array to get length of + * + * @return length of array + */ + +size_t +heim_array_get_length(heim_array_t array) +{ + return array->len; +} + +/** + * Get value of element at array index + * + * @param array array copy object from + * @param idx index of object, 0 based, must be smaller then + * heim_array_get_length() + * + * @return a not-retained copy of the object + */ + +heim_object_t +heim_array_get_value(heim_array_t array, size_t idx) +{ + if (idx >= array->len) + heim_abort("index too large"); + return array->val[idx]; +} + +/** + * Get value of element at array index + * + * @param array array copy object from + * @param idx index of object, 0 based, must be smaller then + * heim_array_get_length() + * + * @return a retained copy of the object + */ + +heim_object_t +heim_array_copy_value(heim_array_t array, size_t idx) +{ + if (idx >= array->len) + heim_abort("index too large"); + return heim_retain(array->val[idx]); +} + +/** + * Set value at array index + * + * @param array array copy object from + * @param idx index of object, 0 based, must be smaller then + * heim_array_get_length() + * @param value value to set + * + */ + +void +heim_array_set_value(heim_array_t array, size_t idx, heim_object_t value) +{ + if (idx >= array->len) + heim_abort("index too large"); + heim_release(array->val[idx]); + array->val[idx] = heim_retain(value); +} + +/** + * Delete value at idx + * + * @param array the array to modify + * @param idx the key to delete + */ + +void +heim_array_delete_value(heim_array_t array, size_t idx) +{ + heim_object_t obj; + if (idx >= array->len) + heim_abort("index too large"); + obj = array->val[idx]; + + array->len--; + + /* + * Deleting the first or last elements is cheap, as we leave + * allocated space for opportunistic reuse later; no realloc(), no + * memmove(). All others require a memmove(). + * + * If we ever need to optimize deletion of non-last/ non-first + * element we can use a tagged object type to signify "deleted + * value" so we can leave holes in the array, avoid memmove()s on + * delete, and opportunistically re-use those holes on insert. + */ + if (idx == 0) + array->val++; + else if (idx < array->len) + (void) memmove(&array->val[idx], &array->val[idx + 1], + (array->len - idx) * sizeof(array->val[0])); + + heim_release(obj); +} + +/** + * Filter out entres of array when function return true + * + * @param array the array to modify + * @param fn filter function + */ + +void +heim_array_filter_f(heim_array_t array, void *ctx, heim_array_filter_f_t fn) +{ + size_t n = 0; + + while (n < array->len) { + if (fn(array->val[n], ctx)) { + heim_array_delete_value(array, n); + } else { + n++; + } + } +} + +#ifdef __BLOCKS__ + +/** + * Filter out entres of array when block return true + * + * @param array the array to modify + * @param block filter block + */ + +void +heim_array_filter(heim_array_t array, int (^block)(heim_object_t)) +{ + size_t n = 0; + + while (n < array->len) { + if (block(array->val[n])) { + heim_array_delete_value(array, n); + } else { + n++; + } + } +} + +#endif /* __BLOCKS__ */ diff --git a/third_party/heimdal/lib/base/baselocl.h b/third_party/heimdal/lib/base/baselocl.h new file mode 100644 index 0000000..7ca6439 --- /dev/null +++ b/third_party/heimdal/lib/base/baselocl.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2010 - 2011 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" + +#include + +#define ISTILDE(x) (x == '~') +#ifdef _WIN32 +# define ISPATHSEP(x) (x == '/' || x =='\\') +#else +# define ISPATHSEP(x) (x == '/') +#endif + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#define HEIMDAL_TEXTDOMAIN "heimdal_krb5" + +#ifdef LIBINTL +#include +#define N_(x,y) dgettext(HEIMDAL_TEXTDOMAIN, x) +#else +#define N_(x,y) (x) +#define bindtextdomain(package, localedir) +#endif + +#include "heimqueue.h" +#include "heim_threads.h" +#include "heimbase.h" +#include "heimbasepriv.h" + +#ifdef HAVE_DISPATCH_DISPATCH_H +#include +#endif + +/* tagged strings/object/XXX */ +#define heim_base_is_tagged(x) (((uintptr_t)(x)) & 0x3) + +#define heim_base_is_tagged_object(x) ((((uintptr_t)(x)) & 0x3) == 1) +#define heim_base_make_tagged_object(x, tid) \ + ((heim_object_t)((((uintptr_t)(x)) << 5) | ((tid) << 2) | 0x1)) +#define heim_base_tagged_object_tid(x) ((((uintptr_t)(x)) & 0x1f) >> 2) +#define heim_base_tagged_object_value(x) (((uintptr_t)(x)) >> 5) + +/* + * + */ + +#undef HEIMDAL_NORETURN_ATTRIBUTE +#define HEIMDAL_NORETURN_ATTRIBUTE +#undef HEIMDAL_PRINTF_ATTRIBUTE +#define HEIMDAL_PRINTF_ATTRIBUTE(x) + +struct heim_context_s { + heim_log_facility *log_dest; + heim_log_facility *warn_dest; + heim_log_facility *debug_dest; + char *time_fmt; + unsigned int log_utc:1; + unsigned int homedir_access:1; + struct et_list *et_list; + char *error_string; + heim_error_code error_code; +}; diff --git a/third_party/heimdal/lib/base/bool.c b/third_party/heimdal/lib/base/bool.c new file mode 100644 index 0000000..f7b66ee --- /dev/null +++ b/third_party/heimdal/lib/base/bool.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" + +struct heim_type_data _heim_bool_object = { + HEIM_TID_BOOL, + "bool-object", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +heim_bool_t +heim_bool_create(int val) +{ + return heim_base_make_tagged_object(!!val, HEIM_TID_BOOL); +} + +int +heim_bool_val(heim_bool_t ptr) +{ + return heim_base_tagged_object_value(ptr); +} diff --git a/third_party/heimdal/lib/base/bsearch.c b/third_party/heimdal/lib/base/bsearch.c new file mode 100644 index 0000000..268cc01 --- /dev/null +++ b/third_party/heimdal/lib/base/bsearch.c @@ -0,0 +1,888 @@ +/* + * Copyright (c) 2011, Secure Endpoints Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "baselocl.h" + +#include +#include +#ifdef HAVE_IO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include +#ifdef HAVE_STRINGS_H +#include +#endif +#include +#include + +/* + * This file contains functions for binary searching flat text in memory + * and in text files where each line is a [variable length] record. + * Each record has a key and an optional value separated from the key by + * unquoted whitespace. Whitespace in the key, and leading whitespace + * for the value, can be quoted with backslashes (but CR and LF must be + * quoted in such a way that they don't appear in the quoted result). + * + * Binary searching a tree are normally a dead simple algorithm. It + * turns out that binary searching flat text with *variable* length + * records is... tricky. There's no indexes to record beginning bytes, + * thus any index selected during the search is likely to fall in the + * middle of a record. When deciding to search a left sub-tree one + * might fail to find the last record in that sub-tree on account of the + * right boundary falling in the middle of it -- the chosen solution to + * this makes left sub-tree searches slightly less efficient than right + * sub-tree searches. + * + * If binary searching flat text in memory is tricky, using block-wise + * I/O instead is trickier! But it's necessary in order to support + * large files (which we either can't or wouldn't want to read or map + * into memory). Each block we read has to be large enough that the + * largest record can fit in it. And each block might start and/or end + * in the middle of a record. Here it is the right sub-tree searches + * that are less efficient than left sub-tree searches. + * + * bsearch_common() contains the common text block binary search code. + * + * _bsearch_text() is the interface for searching in-core text. + * _bsearch_file() is the interface for block-wise searching files. + */ + +struct bsearch_file_handle { + int fd; /* file descriptor */ + char *cache; /* cache bytes */ + char *page; /* one double-size page worth of bytes */ + size_t file_sz; /* file size */ + size_t cache_sz; /* cache size */ + size_t page_sz; /* page size */ +}; + +/* Find a new-line */ +static const char * +find_line(const char *buf, size_t i, size_t right) +{ + if (i == 0) + return &buf[i]; + for (; i < right; i++) { + if (buf[i] == '\n') { + if ((i + 1) < right) + return &buf[i + 1]; + return NULL; + } + } + return NULL; +} + +/* + * Common routine for binary searching text in core. + * + * Perform a binary search of a char array containing a block from a + * text file where each line is a record (LF and CRLF supported). Each + * record consists of a key followed by an optional value separated from + * the key by whitespace. Whitespace can be quoted with backslashes. + * It's the caller's responsibility to encode/decode keys/values if + * quoting is desired; newlines should be encoded such that a newline + * does not appear in the result. + * + * All output arguments are optional. + * + * Returns 0 if key is found, -1 if not found, or an error code such as + * ENOMEM in case of error. + * + * Inputs: + * + * @buf String to search + * @sz Size of string to search + * @key Key string to search for + * @buf_is_start True if the buffer starts with a record, false if it + * starts in the middle of a record or if the caller + * doesn't know. + * + * Outputs: + * + * @value Location to store a copy of the value (caller must free) + * @location Record location if found else the location where the + * record should be inserted (index into @buf) + * @cmp Set to less than or greater than 0 to indicate that a + * key not found would have fit in an earlier or later + * part of a file. Callers should use this to decide + * whether to read a block to the left or to the right and + * search that. + * @loops Location to store a count of bisections required for + * search (useful for confirming logarithmic performance) + */ +static int +bsearch_common(const char *buf, size_t sz, const char *key, + int buf_is_start, char **value, size_t *location, + int *cmp, size_t *loops) +{ + const char *linep; + size_t key_start, key_len; /* key string in buf */ + size_t val_start, val_len; /* value string in buf */ + int key_cmp = -1; + size_t k; + size_t l; /* left side of buffer for binary search */ + size_t r; /* right side of buffer for binary search */ + size_t rmax; /* right side of buffer for binary search */ + size_t i; /* index into buffer, typically in the middle of l and r */ + size_t loop_count = 0; + int ret = -1; + + if (value) + *value = NULL; + if (cmp) + *cmp = 0; + if (loops) + *loops = 0; + + /* Binary search; file should be sorted */ + for (l = 0, r = rmax = sz, i = sz >> 1; i >= l && i < rmax; loop_count++) { + heim_assert(i < sz, "invalid aname2lname db index"); + + /* buf[i] is likely in the middle of a line; find the next line */ + linep = find_line(buf, i, rmax); + k = linep ? linep - buf : i; + if (linep == NULL || k >= rmax) { + /* + * No new line found to the right; search to the left then + * but don't change rmax (this isn't optimal, but it's + * simple). + */ + if (i == l) + break; + r = i; + i = l + ((r - l) >> 1); + continue; + } + i = k; + heim_assert(i >= l && i < rmax, "invalid aname2lname db index"); + + /* Got a line; check it */ + + /* Search for and split on unquoted whitespace */ + val_start = 0; + for (key_start = i, key_len = 0, val_len = 0, k = i; k < rmax; k++) { + if (buf[k] == '\\') { + k++; + continue; + } + if (buf[k] == '\r' || buf[k] == '\n') { + /* We now know where the key ends, and there's no value */ + key_len = k - i; + break; + } + if (!isspace((unsigned char)buf[k])) + continue; + + while (k < rmax && isspace((unsigned char)buf[k])) { + key_len = k - i; + k++; + } + if (k < rmax) + val_start = k; + /* Find end of value */ + for (; k < rmax && buf[k] != '\0'; k++) { + if (buf[k] == '\r' || buf[k] == '\n') { + val_len = k - val_start; + break; + } + } + break; + } + + /* + * The following logic is for dealing with partial buffers, + * which we use for block-wise binary searches of large files + */ + if (key_start == 0 && !buf_is_start) { + /* + * We're at the beginning of a block that might have started + * in the middle of a record whose "key" might well compare + * as greater than the key we're looking for, so we don't + * bother comparing -- we know key_cmp must be -1 here. + */ + key_cmp = -1; + break; + } + if ((val_len && buf[val_start + val_len] != '\n') || + (!val_len && buf[key_start + key_len] != '\n')) { + /* + * We're at the end of a block that ends in the middle of a + * record whose "key" might well compare as less than the + * key we're looking for, so we don't bother comparing -- we + * know key_cmp must be >= 0 but we can't tell. Our caller + * will end up reading a double-size block to handle this. + */ + key_cmp = 1; + break; + } + + key_cmp = strncmp(key, &buf[key_start], key_len); + if (key_cmp == 0 && strlen(key) != key_len) + key_cmp = 1; + if (key_cmp < 0) { + /* search left */ + r = rmax = (linep - buf); + i = l + ((r - l) >> 1); + if (location) + *location = key_start; + } else if (key_cmp > 0) { + /* search right */ + if (l == i) + break; /* not found */ + l = i; + i = l + ((r - l) >> 1); + if (location) + *location = val_start + val_len; + } else { + /* match! */ + if (location) + *location = key_start; + ret = 0; + if (val_len && value) { + /* Avoid strndup() so we don't need libroken here yet */ + if ((*value = malloc(val_len + 1))) { + (void) memcpy(*value, &buf[val_start], val_len); + (*value)[val_len] = '\0'; + } else { + ret = errno; + } + } + break; + } + } + + if (cmp) + *cmp = key_cmp; + if (loops) + *loops = loop_count; + + return ret; +} + +/* + * Binary search a char array containing sorted text records separated + * by new-lines (or CRLF). Each record consists of a key and an + * optional value following the key, separated from the key by unquoted + * whitespace. + * + * All output arguments are optional. + * + * Returns 0 if key is found, -1 if not found, or an error code such as + * ENOMEM in case of error. + * + * Inputs: + * + * @buf Char array pointer + * @buf_sz Size of buf + * @key Key to search for + * + * Outputs: + * + * @value Location where to put the value, if any (caller must free) + * @location Record location if found else the location where the record + * should be inserted (index into @buf) + * @loops Location where to put a number of loops (or comparisons) + * needed for the search (useful for benchmarking) + */ +int +_bsearch_text(const char *buf, size_t buf_sz, const char *key, + char **value, size_t *location, size_t *loops) +{ + return bsearch_common(buf, buf_sz, key, 1, value, location, NULL, loops); +} + +#define MAX_BLOCK_SIZE (1024 * 1024) +#define DEFAULT_MAX_FILE_SIZE (1024 * 1024) +/* + * Open a file for binary searching. The file will be read in entirely + * if it is smaller than @max_sz, else a cache of @max_sz bytes will be + * allocated. + * + * Returns 0 on success, else an error number or -1 if the file is empty. + * + * Inputs: + * + * @fname Name of file to open + * @max_sz Maximum size of cache to allocate, in bytes (if zero, default) + * @page_sz Page size (must be a power of two, larger than 256, smaller + * than 1MB; if zero use default) + * + * Outputs: + * + * @bfh Handle for use with _bsearch_file() and _bsearch_file_close() + * @reads Number of reads performed + */ +int +_bsearch_file_open(const char *fname, size_t max_sz, size_t page_sz, + bsearch_file_handle *bfh, size_t *reads) +{ + bsearch_file_handle new_bfh = NULL; + struct stat st; + size_t i; + int fd; + int ret; + + *bfh = NULL; + + if (reads) + *reads = 0; + + fd = open(fname, O_RDONLY); + if (fd == -1) + return errno; + + if (fstat(fd, &st) == -1) { + ret = errno; + goto err; + } + + if (st.st_size == 0) { + ret = -1; /* no data -> no binary search */ + goto err; + } + + /* Validate / default arguments */ + if (max_sz == 0) + max_sz = DEFAULT_MAX_FILE_SIZE; + for (i = page_sz; i; i >>= 1) { + /* Make sure page_sz is a power of two */ + if ((i % 2) && (i >> 1)) { + page_sz = 0; + break; + } + } + if (page_sz == 0) +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + page_sz = st.st_blksize; +#else + page_sz = 4096; +#endif + for (i = page_sz; i; i >>= 1) { + /* Make sure page_sz is a power of two */ + if ((i % 2) && (i >> 1)) { + /* Can't happen! Filesystems always use powers of two! */ + page_sz = 4096; + break; + } + } + if (page_sz > MAX_BLOCK_SIZE) + page_sz = MAX_BLOCK_SIZE; + + new_bfh = calloc(1, sizeof (*new_bfh)); + if (new_bfh == NULL) { + ret = ENOMEM; + goto err; + } + + new_bfh->fd = fd; + new_bfh->page_sz = page_sz; + new_bfh->file_sz = st.st_size; + + if (max_sz >= st.st_size) { + /* Whole-file method */ + new_bfh->cache = malloc(st.st_size + 1); + if (new_bfh->cache) { + new_bfh->cache[st.st_size] = '\0'; + new_bfh->cache_sz = st.st_size; + ret = read(fd, new_bfh->cache, st.st_size); + if (ret < 0) { + ret = errno; + goto err; + } + if (ret != st.st_size) { + ret = EIO; /* XXX ??? */ + goto err; + } + if (reads) + *reads = 1; + (void) close(fd); + new_bfh->fd = -1; + *bfh = new_bfh; + return 0; + } + } + + /* Block-size method, or above malloc() failed */ + new_bfh->page = malloc(new_bfh->page_sz << 1); + if (new_bfh->page == NULL) { + /* Can't even allocate a single double-size page! */ + ret = ENOMEM; + goto err; + } + + new_bfh->cache_sz = max_sz < st.st_size ? max_sz : st.st_size; + new_bfh->cache = malloc(new_bfh->cache_sz); + *bfh = new_bfh; + + /* + * malloc() may have failed because we were asking for a lot of + * memory, but we may still be able to operate without a cache, + * so let's not fail. + */ + if (new_bfh->cache == NULL) { + new_bfh->cache_sz = 0; + return 0; + } + + /* Initialize cache */ + for (i = 0; i < new_bfh->cache_sz; i += new_bfh->page_sz) + new_bfh->cache[i] = '\0'; + return 0; + +err: + (void) close(fd); + if (new_bfh) { + free(new_bfh->page); + free(new_bfh->cache); + free(new_bfh); + } + return ret; +} + +/* + * Indicate whether the given binary search file handle will be searched + * with block-wise method. + */ +void +_bsearch_file_info(bsearch_file_handle bfh, + size_t *page_sz, size_t *max_sz, int *blockwise) +{ + if (page_sz) + *page_sz = bfh->page_sz; + if (max_sz) + *max_sz = bfh->cache_sz; + if (blockwise) + *blockwise = (bfh->file_sz != bfh->cache_sz); +} + +/* + * Close the given binary file search handle. + * + * Inputs: + * + * @bfh Pointer to variable containing handle to close. + */ +void +_bsearch_file_close(bsearch_file_handle *bfh) +{ + if (!*bfh) + return; + if ((*bfh)->fd >= 0) + (void) close((*bfh)->fd); + if ((*bfh)->page) + free((*bfh)->page); + if ((*bfh)->cache) + free((*bfh)->cache); + free(*bfh); + *bfh = NULL; +} + +/* + * Private function to get a page from a cache. The cache is a char + * array of 2^n - 1 double-size page worth of bytes, where n is the + * number of tree levels that the cache stores. The cache can be + * smaller than n implies. + * + * The page may or may not be valid. If the first byte of it is NUL + * then it's not valid, else it is. + * + * Returns 1 if page is in cache and valid, 0 if the cache is too small + * or the page is invalid. The page address is output in @buf if the + * cache is large enough to contain it regardless of whether the page is + * valid. + * + * Inputs: + * + * @bfh Binary search file handle + * @level Level in the tree that we want a page for + * @page_idx Page number in the given level (0..2^level - 1) + * + * Outputs: + * + * @buf Set to address of page if the cache is large enough + */ +static int +get_page_from_cache(bsearch_file_handle bfh, size_t level, size_t page_idx, + char **buf) +{ + size_t idx = 0; + size_t page_sz; + + page_sz = bfh->page_sz << 1; /* we use double-size pages in the cache */ + + *buf = NULL; + + /* + * Compute index into cache. The cache is basically an array of + * double-size pages. The first (zeroth) double-size page in the + * cache will be the middle page of the file -- the root of the + * tree. The next two double-size pages will be the left and right + * pages of the second level in the tree. The next four double-size + * pages will be the four pages at the next level. And so on for as + * many pages as fit in the cache. + * + * The page index is the number of the page at the given level. We + * then compute (2^level - 1 + page index) * 2page size, check that + * we have that in the cache, check that the page has been read (it + * doesn't start with NUL). + */ + if (level) + idx = (1 << level) - 1 + page_idx; + if (((idx + 1) * page_sz * 2) > bfh->cache_sz) + return 0; + + *buf = &bfh->cache[idx * page_sz * 2]; + if (bfh->cache[idx * page_sz * 2] == '\0') + return 0; /* cache[idx] == NUL -> page not loaded in cache */ + return 1; +} + +/* + * Private function to read a page of @page_sz from @fd at offset @off + * into @buf, outputing the number of bytes read, which will be the same + * as @page_sz unless the page being read is the last page, in which + * case the number of remaining bytes in the file will be output. + * + * Returns 0 on success or an errno value otherwise (EIO if reads are + * short). + * + * Inputs: + * + * @bfh Binary search file handle + * @level Level in the binary search tree that we're at + * @page_idx Page "index" at the @level of the tree that we want + * @page Actual page number that we want + * want_double Whether we need a page or double page read + * + * Outputs: + * + * @buf Page read or cached + * @bytes Bytes read (may be less than page or double page size in + * the case of the last page, of course) + */ +static int +read_page(bsearch_file_handle bfh, size_t level, size_t page_idx, size_t page, + int want_double, const char **buf, size_t *bytes) +{ + int ret; + off_t off; + size_t expected; + size_t wanted; + char *page_buf; + + /* Figure out where we're reading and how much */ + off = page * bfh->page_sz; + if (off < 0) + return EOVERFLOW; + + wanted = bfh->page_sz << want_double; + expected = ((bfh->file_sz - off) > wanted) ? wanted : bfh->file_sz - off; + + if (get_page_from_cache(bfh, level, page_idx, &page_buf)) { + *buf = page_buf; + *bytes = expected; + return 0; /* found in cache */ + } + + + *bytes = 0; + *buf = NULL; + + /* OK, we have to read a page or double-size page */ + + if (page_buf) + want_double = 1; /* we'll be caching; we cache double-size pages */ + else + page_buf = bfh->page; /* we won't cache this page */ + + wanted = bfh->page_sz << want_double; + expected = ((bfh->file_sz - off) > wanted) ? wanted : bfh->file_sz - off; + +#ifdef HAVE_PREAD + ret = pread(bfh->fd, page_buf, expected, off); +#else + if (lseek(bfh->fd, off, SEEK_SET) == (off_t)-1) + return errno; + ret = read(bfh->fd, page_buf, expected); +#endif + if (ret < 0) + return errno; + + if (ret != expected) + return EIO; /* XXX ??? */ + + *buf = page_buf; + *bytes = expected; + return 0; +} + +/* + * Perform a binary search of a file where each line is a record (LF and + * CRLF supported). Each record consists of a key followed by an + * optional value separated from the key by whitespace. Whitespace can + * be quoted with backslashes. It's the caller's responsibility to + * encode/decode keys/values if quoting is desired; newlines should be + * encoded such that a newline does not appear in the result. + * + * The search is done with block-wise I/O (i.e., the whole file is not + * read into memory). + * + * All output arguments are optional. + * + * Returns 0 if key is found, -1 if not found, or an error code such as + * ENOMEM in case of error. + * + * NOTE: We could improve this by not freeing the buffer, instead + * requiring that the caller provide it. Further, we could cache + * the top N levels of [double-size] pages (2^N - 1 pages), which + * should speed up most searches by reducing the number of reads + * by N. + * + * Inputs: + * + * @fd File descriptor (file to search) + * @page_sz Page size (if zero then the file's st_blksize will be used) + * @key Key string to search for + * + * Outputs: + * + * @value Location to store a copy of the value (caller must free) + * @location Record location if found else the location where the + * record should be inserted (index into @buf) + * @loops Location to store a count of bisections required for + * search (useful for confirming logarithmic performance) + * @reads Location to store a count of pages read during search + * (useful for confirming logarithmic performance) + */ +int +_bsearch_file(bsearch_file_handle bfh, const char *key, + char **value, size_t *location, size_t *loops, size_t *reads) +{ + int ret; + const char *buf; + size_t buf_sz; + size_t page, l, r; + size_t my_reads = 0; + size_t my_loops_total = 0; + size_t my_loops; + size_t level; /* level in the tree */ + size_t page_idx = 0; /* page number in the tree level */ + size_t buf_location; + int cmp; + int buf_ends_in_eol = 0; + int buf_is_start = 0; + + if (reads) + *reads = 0; + if (value) + *value = NULL; + if (loops) + *loops = 0; + + /* If whole file is in memory then search that and we're done */ + if (bfh->file_sz == bfh->cache_sz) + return _bsearch_text(bfh->cache, bfh->cache_sz, key, value, location, loops); + + /* Else block-wise binary search */ + + l = 0; + r = (bfh->file_sz / bfh->page_sz) + 1; + for (level = 0, page = r >> 1; page >= l && page < r ; level++) { + ret = read_page(bfh, level, page_idx, page, 0, &buf, &buf_sz); + if (ret != 0) + return ret; + my_reads++; + if (buf[buf_sz - 1] == '\r' || buf[buf_sz - 1] == '\n') + buf_ends_in_eol = 1; + else + buf_ends_in_eol = 0; + + buf_is_start = page == 0 ? 1 : 0; + ret = bsearch_common(buf, (size_t)buf_sz, key, buf_is_start, + value, &buf_location, &cmp, &my_loops); + if (ret > 0) + return ret; + /* Found or no we update stats */ + my_loops_total += my_loops; + if (loops) + *loops = my_loops_total; + if (reads) + *reads = my_reads; + if (location) + *location = page * bfh->page_sz + buf_location; + if (ret == 0) + return 0; /* found! */ + /* Not found */ + if (cmp < 0) { + /* Search left */ + page_idx <<= 1; + r = page; + page = l + ((r - l) >> 1); + continue; + } else { + /* + * Search right, but first search the current and next + * blocks in case that the record we're looking for either + * straddles the boundary between this and the next record, + * or in case the record starts exactly at the next page. + */ + heim_assert(cmp > 0, "cmp > 0"); + + if (!buf_ends_in_eol || page == l || page == (r - 1)) { + ret = read_page(bfh, level, page_idx, page, 1, &buf, &buf_sz); + if (ret != 0) + return ret; + my_reads++; + + buf_is_start = page == l ? 1 : 0; + + ret = bsearch_common(buf, (size_t)buf_sz, key, buf_is_start, + value, &buf_location, &cmp, &my_loops); + if (ret > 0) + return ret; + my_loops_total += my_loops; + if (loops) + *loops = my_loops_total; + if (reads) + *reads = my_reads; + if (location) + *location = page * bfh->page_sz + buf_location; + if (ret == 0) + return 0; + } + + /* Oh well, search right */ + if (l == page && r == (l + 1)) + break; + page_idx = (page_idx << 1) + 1; + l = page; + page = l + ((r - l) >> 1); + continue; + } + } + return -1; +} + + +static int +stdb_open(void *plug, const char *dbtype, const char *dbname, + heim_dict_t options, void **db, heim_error_t *error) +{ + bsearch_file_handle bfh; + char *p; + int ret; + + if (error) + *error = NULL; + if (dbname == NULL || *dbname == '\0') { + if (error) + *error = heim_error_create(EINVAL, + N_("DB name required for sorted-text DB " + "plugin", "")); + return EINVAL; + } + p = strrchr(dbname, '.'); + if (p == NULL || strcmp(p, ".txt") != 0) { + if (error) + *error = heim_error_create(ENOTSUP, + N_("Text file (name ending in .txt) " + "required for sorted-text DB plugin", + "")); + return ENOTSUP; + } + + ret = _bsearch_file_open(dbname, 0, 0, &bfh, NULL); + if (ret) + return ret; + + *db = bfh; + return 0; +} + +static int +stdb_close(void *db, heim_error_t *error) +{ + bsearch_file_handle bfh = db; + + if (error) + *error = NULL; + _bsearch_file_close(&bfh); + return 0; +} + +static heim_data_t +stdb_copy_value(void *db, heim_string_t table, heim_data_t key, + heim_error_t *error) +{ + bsearch_file_handle bfh = db; + const char *k; + char *v = NULL; + heim_data_t value; + int ret; + + if (error) + *error = NULL; + + if (table == NULL) + table = HSTR(""); + + if (table != HSTR("")) + return NULL; + + if (heim_get_tid(key) == HEIM_TID_STRING) + k = heim_string_get_utf8((heim_string_t)key); + else + k = (const char *)heim_data_get_ptr(key); + ret = _bsearch_file(bfh, k, &v, NULL, NULL, NULL); + if (ret == 0 && v == NULL) + ret = -1; /* Quiet lint */ + if (ret != 0) { + if (ret > 0 && error) + *error = heim_error_create(ret, "%s", strerror(ret)); + return NULL; + } + value = heim_data_create(v, strlen(v)); + free(v); + /* XXX Handle ENOMEM */ + return value; +} + +struct heim_db_type heim_sorted_text_file_dbtype = { + 1, stdb_open, NULL, stdb_close, NULL, NULL, NULL, NULL, NULL, NULL, + stdb_copy_value, NULL, NULL, NULL +}; diff --git a/third_party/heimdal/lib/base/common_plugin.h b/third_party/heimdal/lib/base/common_plugin.h new file mode 100644 index 0000000..d7b6bca --- /dev/null +++ b/third_party/heimdal/lib/base/common_plugin.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2006 - 2020 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2018 AuriStor, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef HEIMDAL_BASE_COMMON_PLUGIN_H +#define HEIMDAL_BASE_COMMON_PLUGIN_H + +#include + +#ifdef _WIN32 +# ifndef HEIM_CALLCONV +# define HEIM_CALLCONV __stdcall +# endif +# ifndef HEIM_LIB_CALL +# define HEIM_LIB_CALL __stdcall +# endif +#else +# ifndef HEIM_CALLCONV +# define HEIM_CALLCONV +# endif +# ifndef HEIM_LIB_CALL +# define HEIM_LIB_CALL +# endif +#endif +#ifndef KRB5_CALLCONV +# define KRB5_CALLCONV HEIM_CALLCONV +#endif +#ifndef KRB5_LIB_CALL +# define KRB5_LIB_CALL HEIM_LIB_CALL +#endif + +/* For krb5 plugins, this is a krb5_context */ +typedef struct heim_pcontext_s *heim_pcontext; + +typedef uintptr_t +(HEIM_LIB_CALL *heim_get_instance_func_t)(const char *); +typedef heim_get_instance_func_t krb5_get_instance_t; + +/* + * All plugin function tables extend the following structure. + */ +struct heim_plugin_common_ftable_desc { + HEIM_PLUGIN_FTABLE_COMMON_ELEMENTS(heim_pcontext); +}; +typedef struct heim_plugin_common_ftable_desc heim_plugin_common_ftable; +typedef struct heim_plugin_common_ftable_desc *heim_plugin_common_ftable_p; +typedef const struct heim_plugin_common_ftable_desc *heim_plugin_common_ftable_const_p; +typedef struct heim_plugin_common_ftable_desc * const heim_plugin_common_ftable_cp; + +typedef int +(HEIM_CALLCONV heim_plugin_load_ft)(heim_pcontext context, + heim_get_instance_func_t *func, + size_t *n_ftables, + heim_plugin_common_ftable_cp **ftables); + +typedef heim_plugin_load_ft *heim_plugin_load_t; + +/* For source backwards-compatibility */ +typedef struct heim_plugin_common_ftable_desc krb5_plugin_common_ftable; +typedef struct heim_plugin_common_ftable_desc *krb5_plugin_common_ftable_p; +typedef struct heim_plugin_common_ftable_desc * const krb5_plugin_common_ftable_cp; +typedef heim_plugin_load_ft krb5_plugin_load_ft; +typedef heim_plugin_load_ft *krb5_plugin_load_t; + +/* + * All plugins must export a function named "_plugin_load" with + * a signature of: + * + * int HEIM_CALLCONV + * _plugin_load(heim_pcontext context, + * heim_get_instance_func_t *func, + * size_t *n_ftables, + * const heim_plugin_common_ftable *const **ftables); + */ +#endif /* HEIMDAL_BASE_COMMON_PLUGIN_H */ diff --git a/third_party/heimdal/lib/base/config_file.c b/third_party/heimdal/lib/base/config_file.c new file mode 100644 index 0000000..b1675ea --- /dev/null +++ b/third_party/heimdal/lib/base/config_file.c @@ -0,0 +1,1472 @@ +/* + * Copyright (c) 1997 - 2004 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" +#include +#include +#include + +#if defined(HAVE_FRAMEWORK_COREFOUNDATION) +#include +#endif + +/* Gaah! I want a portable funopen */ +struct fileptr { + heim_context context; + const char *s; + FILE *f; +}; + +static char * +config_fgets(char *str, size_t len, struct fileptr *ptr) +{ + /* XXX this is not correct, in that they don't do the same if the + line is longer than len */ + if(ptr->f != NULL) + return fgets(str, len, ptr->f); + else { + /* this is almost strsep_copy */ + const char *p; + ssize_t l; + if(*ptr->s == '\0') + return NULL; + p = ptr->s + strcspn(ptr->s, "\n"); + if(*p == '\n') + p++; + l = min(len, (size_t)(p - ptr->s)); + if(len > 0) { + memcpy(str, ptr->s, l); + str[l] = '\0'; + } + ptr->s = p; + return str; + } +} + +static heim_error_code parse_section(char *p, heim_config_section **s, + heim_config_section **res, + const char **err_message); +static heim_error_code parse_binding(struct fileptr *f, unsigned *lineno, char *p, + heim_config_binding **b, + heim_config_binding **parent, + const char **err_message); +static heim_error_code parse_list(struct fileptr *f, unsigned *lineno, + heim_config_binding **parent, + const char **err_message); + +heim_config_section * +heim_config_get_entry(heim_config_section **parent, const char *name, int type) +{ + heim_config_section **q; + + for (q = parent; *q != NULL; q = &(*q)->next) + if (type == heim_config_list && + (unsigned)type == (*q)->type && + strcmp(name, (*q)->name) == 0) + return *q; + *q = calloc(1, sizeof(**q)); + if (*q == NULL) + return NULL; + (*q)->name = strdup(name); + (*q)->type = type; + if ((*q)->name == NULL) { + free(*q); + *q = NULL; + return NULL; + } + return *q; +} + +/* + * Parse a section: + * + * [section] + * foo = bar + * b = { + * a + * } + * ... + * + * starting at the line in `p', storing the resulting structure in + * `s' and hooking it into `parent'. + * Store the error message in `err_message'. + */ + +static heim_error_code +parse_section(char *p, heim_config_section **s, heim_config_section **parent, + const char **err_message) +{ + char *p1; + heim_config_section *tmp; + + p1 = strchr (p + 1, ']'); + if (p1 == NULL) { + *err_message = "missing ]"; + return HEIM_ERR_CONFIG_BADFORMAT; + } + *p1 = '\0'; + tmp = heim_config_get_entry(parent, p + 1, heim_config_list); + if(tmp == NULL) { + *err_message = "out of memory"; + return HEIM_ERR_CONFIG_BADFORMAT; + } + *s = tmp; + return 0; +} + +/* + * Parse a brace-enclosed list from `f', hooking in the structure at + * `parent'. + * Store the error message in `err_message'. + */ + +static heim_error_code +parse_list(struct fileptr *f, unsigned *lineno, heim_config_binding **parent, + const char **err_message) +{ + char buf[2048]; + heim_error_code ret; + heim_config_binding *b = NULL; + unsigned beg_lineno = *lineno; + + while(config_fgets(buf, sizeof(buf), f) != NULL) { + char *p; + + ++*lineno; + buf[strcspn(buf, "\r\n")] = '\0'; + p = buf; + while(isspace((unsigned char)*p)) + ++p; + if (*p == '#' || *p == ';' || *p == '\0') + continue; + while(isspace((unsigned char)*p)) + ++p; + if (*p == '}') + return 0; + if (*p == '\0') + continue; + ret = parse_binding (f, lineno, p, &b, parent, err_message); + if (ret) + return ret; + } + *lineno = beg_lineno; + *err_message = "unclosed {"; + return HEIM_ERR_CONFIG_BADFORMAT; +} + +/* + * + */ + +static heim_error_code +parse_binding(struct fileptr *f, unsigned *lineno, char *p, + heim_config_binding **b, heim_config_binding **parent, + const char **err_message) +{ + heim_config_binding *tmp; + char *p1, *p2; + heim_error_code ret = 0; + + p1 = p; + while (*p && *p != '=' && !isspace((unsigned char)*p)) + ++p; + if (*p == '\0') { + *err_message = "missing ="; + return HEIM_ERR_CONFIG_BADFORMAT; + } + p2 = p; + while (isspace((unsigned char)*p)) + ++p; + if (*p != '=') { + *err_message = "missing ="; + return HEIM_ERR_CONFIG_BADFORMAT; + } + ++p; + while(isspace((unsigned char)*p)) + ++p; + *p2 = '\0'; + if (*p == '{') { + tmp = heim_config_get_entry(parent, p1, heim_config_list); + if (tmp == NULL) { + *err_message = "out of memory"; + return HEIM_ERR_CONFIG_BADFORMAT; + } + ret = parse_list (f, lineno, &tmp->u.list, err_message); + } else { + tmp = heim_config_get_entry(parent, p1, heim_config_string); + if (tmp == NULL) { + *err_message = "out of memory"; + return HEIM_ERR_CONFIG_BADFORMAT; + } + p1 = p; + p = p1 + strlen(p1); + while(p > p1 && isspace((unsigned char)*(p-1))) + --p; + *p = '\0'; + tmp->u.string = strdup(p1); + } + *b = tmp; + return ret; +} + +#if defined(HAVE_FRAMEWORK_COREFOUNDATION) + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 +#define HAVE_CFPROPERTYLISTCREATEWITHSTREAM 1 +#endif + +static char * +cfstring2cstring(CFStringRef string) +{ + CFIndex len; + char *str; + + str = (char *) CFStringGetCStringPtr(string, kCFStringEncodingUTF8); + if (str) + return strdup(str); + + len = CFStringGetLength(string); + len = 1 + CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8); + str = malloc(len); + if (str == NULL) + return NULL; + + if (!CFStringGetCString (string, str, len, kCFStringEncodingUTF8)) { + free (str); + return NULL; + } + return str; +} + +static void +convert_content(const void *key, const void *value, void *context) +{ + heim_config_section *tmp, **parent = context; + char *k; + + if (CFGetTypeID(key) != CFStringGetTypeID()) + return; + + k = cfstring2cstring(key); + if (k == NULL) + return; + + if (CFGetTypeID(value) == CFStringGetTypeID()) { + tmp = heim_config_get_entry(parent, k, heim_config_string); + tmp->u.string = cfstring2cstring(value); + } else if (CFGetTypeID(value) == CFDictionaryGetTypeID()) { + tmp = heim_config_get_entry(parent, k, heim_config_list); + CFDictionaryApplyFunction(value, convert_content, &tmp->u.list); + } else { + /* log */ + } + free(k); +} + +static heim_error_code +parse_plist_config(heim_context context, const char *path, heim_config_section **parent) +{ + CFReadStreamRef s; + CFDictionaryRef d; + CFURLRef url; + + url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)path, strlen(path), 0); + if (url == NULL) { + heim_clear_error_message(context); + return ENOMEM; + } + + s = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); + CFRelease(url); + if (s == NULL) { + heim_clear_error_message(context); + return ENOMEM; + } + + if (!CFReadStreamOpen(s)) { + CFRelease(s); + heim_clear_error_message(context); + return ENOENT; + } + +#ifdef HAVE_CFPROPERTYLISTCREATEWITHSTREAM + d = (CFDictionaryRef)CFPropertyListCreateWithStream(NULL, s, 0, kCFPropertyListImmutable, NULL, NULL); +#else + d = (CFDictionaryRef)CFPropertyListCreateFromStream(NULL, s, 0, kCFPropertyListImmutable, NULL, NULL); +#endif + CFRelease(s); + if (d == NULL) { + heim_clear_error_message(context); + return ENOENT; + } + + CFDictionaryApplyFunction(d, convert_content, parent); + CFRelease(d); + + return 0; +} + +#endif + +static int +is_absolute_path(const char *path) +{ + /* + * An absolute path is one that refers to an explicit object + * without ambiguity. + */ +#ifdef WIN32 + size_t len = strlen(path); + + /* UNC path is by definition absolute */ + if (len > 2 + && ISPATHSEP(path[0]) + && ISPATHSEP(path[1])) + return 1; + + /* A drive letter path might be absolute */ + if (len > 3 + && isalpha((unsigned char)path[0]) + && path[1] == ':' + && ISPATHSEP(path[2])) + return 1; + + /* + * if no drive letter but first char is a path + * separator then the drive letter must be obtained + * from the including file. + */ +#else + /* UNIX is easy, first char '/' is absolute */ + if (ISPATHSEP(path[0])) + return 1; +#endif + return 0; +} + +/* + * Parse the config file `fname', generating the structures into `res' + * returning error messages in `err_message' + */ + +static heim_error_code +heim_config_parse_debug(struct fileptr *f, + heim_config_section **res, + unsigned *lineno, + const char **err_message) +{ + heim_config_section *s = NULL; + heim_config_binding *b = NULL; + char buf[2048]; + heim_error_code ret; + + *lineno = 0; + *err_message = ""; + + while (config_fgets(buf, sizeof(buf), f) != NULL) { + char *p; + + ++*lineno; + buf[strcspn(buf, "\r\n")] = '\0'; + p = buf; + while(isspace((unsigned char)*p)) + ++p; + if (*p == '#' || *p == ';') + continue; + if (*p == '[') { + ret = parse_section(p, &s, res, err_message); + if (ret) + return ret; + b = NULL; + } else if (*p == '}') { + *err_message = "unmatched }"; + return 2048; + } else if (strncmp(p, "include", sizeof("include") - 1) == 0 && + isspace((unsigned char)p[sizeof("include") - 1])) { + p += sizeof("include"); + while (isspace((unsigned char)*p)) + p++; + if (!is_absolute_path(p)) { + heim_set_error_message(f->context, HEIM_ERR_CONFIG_BADFORMAT, + "Configuration include path must be " + "absolute"); + return HEIM_ERR_CONFIG_BADFORMAT; + } + ret = heim_config_parse_file_multi(f->context, p, res); + if (ret) + return ret; + } else if (strncmp(p, "includedir", sizeof("includedir") - 1) == 0 && + isspace((unsigned char)p[sizeof("includedir") - 1])) { + p += sizeof("includedir"); + while (isspace((unsigned char)*p)) + p++; + if (!is_absolute_path(p)) { + heim_set_error_message(f->context, HEIM_ERR_CONFIG_BADFORMAT, + "Configuration includedir path must be " + "absolute"); + return HEIM_ERR_CONFIG_BADFORMAT; + } + ret = heim_config_parse_dir_multi(f->context, p, res); + if (ret) + return ret; + } else if(*p != '\0') { + if (s == NULL) { + *err_message = "binding before section"; + return 2048; + } + ret = parse_binding(f, lineno, p, &b, &s->u.list, err_message); + if (ret) + return ret; + } + } + return 0; +} + +static int +is_plist_file(const char *fname) +{ + size_t len = strlen(fname); + char suffix[] = ".plist"; + if (len < sizeof(suffix)) + return 0; + if (strcasecmp(&fname[len - (sizeof(suffix) - 1)], suffix) != 0) + return 0; + return 1; +} + +/** + * Parse configuration files in the given directory and add the result + * into res. Only files whose names consist only of alphanumeric + * characters, hyphen, and underscore, will be parsed, though files + * ending in ".conf" will also be parsed. + * + * This interface can be used to parse several configuration directories + * into one resulting heim_config_section by calling it repeatably. + * + * @param context a Kerberos 5 context. + * @param dname a directory name to a Kerberos configuration file + * @param res the returned result, must be free with heim_free_config_files(). + * @return Return an error code or 0, see heim_get_error_message(). + * + * @ingroup heim_support + */ + +heim_error_code +heim_config_parse_dir_multi(heim_context context, + const char *dname, + heim_config_section **res) +{ + struct dirent *entry; + heim_error_code ret; + DIR *d; + + if ((d = opendir(dname)) == NULL) + return errno; + + while ((entry = readdir(d)) != NULL) { + char *p = entry->d_name; + char *path; + int is_valid = 1; + + while (*p) { + /* + * Here be dragons. The call to heim_config_parse_file_multi() + * below expands path tokens. Because of the limitations here + * on file naming, we can't have path tokens in the file name, + * so we're safe. Anyone changing this if condition here should + * be aware. + */ + if (!isalnum((unsigned char)*p) && *p != '_' && *p != '-' && + strcmp(p, ".conf") != 0) { + is_valid = 0; + break; + } + p++; + } + if (!is_valid) + continue; + + if (asprintf(&path, "%s/%s", dname, entry->d_name) == -1 || + path == NULL) { + (void) closedir(d); + return heim_enomem(context); + } + ret = heim_config_parse_file_multi(context, path, res); + free(path); + if (ret == ENOMEM) { + (void) closedir(d); + return ENOMEM; + } + /* Ignore malformed config files so we don't lock out admins, etc... */ + } + (void) closedir(d); + return 0; +} + +static int +is_devnull(struct stat *st) +{ +#ifdef WIN32 + return 0; +#else + struct stat devnullst; + + if (stat("/dev/null", &devnullst) == -1) + return 0; + return st->st_dev == devnullst.st_dev && st->st_ino == devnullst.st_ino; +#endif +} + +HEIMDAL_THREAD_LOCAL int config_include_depth = 0; + +/** + * Parse a configuration file and add the result into res. This + * interface can be used to parse several configuration files into one + * resulting heim_config_section by calling it repeatably. + * + * @param context a Kerberos 5 context. + * @param fname a file name to a Kerberos configuration file + * @param res the returned result, must be free with heim_free_config_files(). + * @return Return an error code or 0, see heim_get_error_message(). + * + * @ingroup heim_support + */ + +heim_error_code +heim_config_parse_file_multi(heim_context context, + const char *fname, + heim_config_section **res) +{ + const char *str; + char *newfname = NULL; + unsigned lineno = 0; + heim_error_code ret = 0; + struct fileptr f; + struct stat st; + + if (config_include_depth > 5) { + heim_warnx(context, "Maximum config file include depth reached; " + "not including %s", fname); + return 0; + } + config_include_depth++; + + /** + * If the fname starts with "~/" parse configuration file in the + * current users home directory. The behavior can be disabled and + * enabled by calling heim_set_home_dir_access(). + */ + if (ISTILDE(fname[0]) && ISPATHSEP(fname[1])) { + if (!heim_context_get_homedir_access(context)) { + heim_set_error_message(context, EPERM, + "Access to home directory not allowed"); + ret = EPERM; + goto out; + } + if (asprintf(&newfname, "%%{USERCONFIG}%s", &fname[1]) < 0 || + newfname == NULL) { + ret = heim_enomem(context); + goto out; + } + fname = newfname; + } + + if (is_plist_file(fname)) { +#if defined(HAVE_FRAMEWORK_COREFOUNDATION) + ret = parse_plist_config(context, fname, res); + if (ret) { + heim_set_error_message(context, ret, + "Failed to parse plist %s", fname); + goto out; + } +#else + heim_set_error_message(context, ENOENT, + "no support for plist configuration files"); + ret = ENOENT; + goto out; +#endif + } else { + char *exp_fname = NULL; + + /* + * Note that heim_config_parse_dir_multi() doesn't want tokens + * expanded here, but it happens to limit the names of files to + * include such that there can be no tokens to expand. Don't + * add token expansion for tokens using _, say. + */ + ret = heim_expand_path_tokens(context, fname, 1, &exp_fname, NULL); + if (ret) + goto out; + free(newfname); + fname = newfname = exp_fname; + + f.context = context; + f.f = fopen(fname, "r"); + f.s = NULL; + if (f.f == NULL || fstat(fileno(f.f), &st) == -1) { + if (f.f != NULL) + (void) fclose(f.f); + ret = errno; + heim_set_error_message(context, ret, "open or stat %s: %s", + fname, strerror(ret)); + goto out; + } + + if (!S_ISREG(st.st_mode) && !is_devnull(&st)) { + (void) fclose(f.f); + heim_set_error_message(context, EISDIR, "not a regular file %s: %s", + fname, strerror(EISDIR)); + ret = EISDIR; + goto out; + } + + ret = heim_config_parse_debug(&f, res, &lineno, &str); + fclose(f.f); + if (ret) { + if (ret != HEIM_ERR_CONFIG_BADFORMAT) + ret = HEIM_ERR_CONFIG_BADFORMAT; + heim_set_error_message(context, ret, "%s:%u: %s", + fname, lineno, str); + goto out; + } + } + + out: + config_include_depth--; + if (ret == HEIM_ERR_CONFIG_BADFORMAT || (ret && config_include_depth > 0)) { + heim_warn(context, ret, "Ignoring"); + if (config_include_depth > 0) + ret = 0; + } + free(newfname); + return ret; +} + +heim_error_code +heim_config_parse_file(heim_context context, + const char *fname, + heim_config_section **res) +{ + *res = NULL; + return heim_config_parse_file_multi(context, fname, res); +} + +static void +free_binding(heim_context context, heim_config_binding *b) +{ + heim_config_binding *next_b; + + while (b) { + free (b->name); + assert(b->type == heim_config_string || b->type == heim_config_list); + if (b->type == heim_config_string) + free (b->u.string); + else + free_binding (context, b->u.list); + next_b = b->next; + free (b); + b = next_b; + } +} + +/** + * Free configuration file section, the result of + * heim_config_parse_file() and heim_config_parse_file_multi(). + * + * @param context A Kerberos 5 context + * @param s the configuration section to free + * + * @return returns 0 on successes, otherwise an error code, see + * heim_get_error_message() + * + * @ingroup heim_support + */ + +heim_error_code +heim_config_file_free(heim_context context, heim_config_section *s) +{ + free_binding (context, s); + return 0; +} + +#ifndef HEIMDAL_SMALLER + +heim_error_code +heim_config_copy(heim_context context, + heim_config_section *c, + heim_config_section **head) +{ + heim_config_binding *d, *previous = NULL; + + *head = NULL; + + while (c) { + d = calloc(1, sizeof(*d)); + + if (*head == NULL) + *head = d; + + d->name = strdup(c->name); + d->type = c->type; + assert(d->type == heim_config_string || d->type == heim_config_list); + if (d->type == heim_config_string) + d->u.string = strdup(c->u.string); + else + heim_config_copy (context, c->u.list, &d->u.list); + if (previous) + previous->next = d; + + previous = d; + c = c->next; + } + return 0; +} + +#endif /* HEIMDAL_SMALLER */ + +const void * +heim_config_get_next(heim_context context, + const heim_config_section *c, + const heim_config_binding **pointer, + int type, + ...) +{ + const char *ret; + va_list args; + + va_start(args, type); + ret = heim_config_vget_next(context, c, pointer, type, args); + va_end(args); + return ret; +} + +static const void * +vget_next(heim_context context, + const heim_config_binding *b, + const heim_config_binding **pointer, + int type, + const char *name, + va_list args) +{ + const char *p = va_arg(args, const char *); + + while (b != NULL) { + if (strcmp(b->name, name) == 0) { + if (b->type == (unsigned)type && p == NULL) { + *pointer = b; + return b->u.generic; + } else if (b->type == heim_config_list && p != NULL) { + return vget_next(context, b->u.list, pointer, type, p, args); + } + } + b = b->next; + } + return NULL; +} + +const void * +heim_config_vget_next(heim_context context, + const heim_config_section *c, + const heim_config_binding **pointer, + int type, + va_list args) +{ + const heim_config_binding *b; + const char *p; + + if (c == NULL) + return NULL; + + if (*pointer == NULL) { + /* first time here, walk down the tree looking for the right + section */ + p = va_arg(args, const char *); + if (p == NULL) + return NULL; + return vget_next(context, c, pointer, type, p, args); + } + + /* we were called again, so just look for more entries with the + same name and type */ + for (b = (*pointer)->next; b != NULL; b = b->next) { + if(strcmp(b->name, (*pointer)->name) == 0 && b->type == (unsigned)type) { + *pointer = b; + return b->u.generic; + } + } + return NULL; +} + +const void * +heim_config_get(heim_context context, + const heim_config_section *c, + int type, + ...) +{ + const void *ret; + va_list args; + + va_start(args, type); + ret = heim_config_vget(context, c, type, args); + va_end(args); + return ret; +} + + +const void * +heim_config_vget(heim_context context, + const heim_config_section *c, + int type, + va_list args) +{ + const heim_config_binding *foo = NULL; + + return heim_config_vget_next(context, c, &foo, type, args); +} + +/** + * Get a list of configuration binding list for more processing + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param ... a list of names, terminated with NULL. + * + * @return NULL if configuration list is not found, a list otherwise + * + * @ingroup heim_support + */ + +const heim_config_binding * +heim_config_get_list(heim_context context, + const heim_config_section *c, + ...) +{ + const heim_config_binding *ret; + va_list args; + + va_start(args, c); + ret = heim_config_vget_list(context, c, args); + va_end(args); + return ret; +} + +/** + * Get a list of configuration binding list for more processing + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param args a va_list of arguments + * + * @return NULL if configuration list is not found, a list otherwise + * + * @ingroup heim_support + */ + +const heim_config_binding * +heim_config_vget_list(heim_context context, + const heim_config_section *c, + va_list args) +{ + return heim_config_vget(context, c, heim_config_list, args); +} + +/** + * Returns a "const char *" to a string in the configuration database. + * The string may not be valid after a reload of the configuration + * database so a caller should make a local copy if it needs to keep + * the string. + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param ... a list of names, terminated with NULL. + * + * @return NULL if configuration string not found, a string otherwise + * + * @ingroup heim_support + */ + +const char * +heim_config_get_string(heim_context context, + const heim_config_section *c, + ...) +{ + const char *ret; + va_list args; + + va_start(args, c); + ret = heim_config_vget_string(context, c, args); + va_end(args); + return ret; +} + +/** + * Like heim_config_get_string(), but uses a va_list instead of ... + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param args a va_list of arguments + * + * @return NULL if configuration string not found, a string otherwise + * + * @ingroup heim_support + */ + +const char * +heim_config_vget_string(heim_context context, + const heim_config_section *c, + va_list args) +{ + return heim_config_vget(context, c, heim_config_string, args); +} + +/** + * Like heim_config_vget_string(), but instead of returning NULL, + * instead return a default value. + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param def_value the default value to return if no configuration + * found in the database. + * @param args a va_list of arguments + * + * @return a configuration string + * + * @ingroup heim_support + */ + +const char * +heim_config_vget_string_default(heim_context context, + const heim_config_section *c, + const char *def_value, + va_list args) +{ + const char *ret; + + ret = heim_config_vget_string(context, c, args); + if (ret == NULL) + ret = def_value; + return ret; +} + +/** + * Like heim_config_get_string(), but instead of returning NULL, + * instead return a default value. + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param def_value the default value to return if no configuration + * found in the database. + * @param ... a list of names, terminated with NULL. + * + * @return a configuration string + * + * @ingroup heim_support + */ + +const char * +heim_config_get_string_default(heim_context context, + const heim_config_section *c, + const char *def_value, + ...) +{ + const char *ret; + va_list args; + + va_start(args, def_value); + ret = heim_config_vget_string_default (context, c, def_value, args); + va_end(args); + return ret; +} + +static char * +next_component_string(char * begin, const char * delims, char **state) +{ + char * end; + + if (begin == NULL) + begin = *state; + + if (*begin == '\0') + return NULL; + + end = begin; + while (*end == '"') { + char * t = strchr(end + 1, '"'); + + if (t) + end = ++t; + else + end += strlen(end); + } + + if (*end != '\0') { + size_t pos; + + pos = strcspn(end, delims); + end = end + pos; + } + + if (*end != '\0') { + *end = '\0'; + *state = end + 1; + if (*begin == '"' && *(end - 1) == '"' && begin + 1 < end) { + begin++; *(end - 1) = '\0'; + } + return begin; + } + + *state = end; + if (*begin == '"' && *(end - 1) == '"' && begin + 1 < end) { + begin++; *(end - 1) = '\0'; + } + return begin; +} + +/** + * Get a list of configuration strings, free the result with + * heim_config_free_strings(). + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param args a va_list of arguments + * + * @return TRUE or FALSE + * + * @ingroup heim_support + */ + +char ** +heim_config_vget_strings(heim_context context, + const heim_config_section *c, + va_list args) +{ + char **strings = NULL; + size_t nstr = 0; + const heim_config_binding *b = NULL; + const char *p; + + while((p = heim_config_vget_next(context, c, &b, + heim_config_string, args))) { + char *tmp = strdup(p); + char *pos = NULL; + char *s; + if(tmp == NULL) + goto cleanup; + s = next_component_string(tmp, " \t", &pos); + while(s){ + char **tmp2 = realloc(strings, (nstr + 1) * sizeof(*strings)); + if(tmp2 == NULL) { + free(tmp); + goto cleanup; + } + strings = tmp2; + strings[nstr] = strdup(s); + nstr++; + if(strings[nstr-1] == NULL) { + free(tmp); + goto cleanup; + } + s = next_component_string(NULL, " \t", &pos); + } + free(tmp); + } + if(nstr){ + char **tmp = realloc(strings, (nstr + 1) * sizeof(*strings)); + if(tmp == NULL) + goto cleanup; + strings = tmp; + strings[nstr] = NULL; + } + return strings; +cleanup: + while(nstr--) + free(strings[nstr]); + free(strings); + return NULL; + +} + +/** + * Get a list of configuration strings, free the result with + * heim_config_free_strings(). + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param ... a list of names, terminated with NULL. + * + * @return TRUE or FALSE + * + * @ingroup heim_support + */ + +char ** +heim_config_get_strings(heim_context context, + const heim_config_section *c, + ...) +{ + va_list ap; + char **ret; + va_start(ap, c); + ret = heim_config_vget_strings(context, c, ap); + va_end(ap); + return ret; +} + +/** + * Free the resulting strings from heim_config-get_strings() and + * heim_config_vget_strings(). + * + * @param strings strings to free + * + * @ingroup heim_support + */ + +void +heim_config_free_strings(char **strings) +{ + char **s = strings; + + while (s && *s) { + free(*s); + s++; + } + free(strings); +} + +/** + * Like heim_config_get_bool_default() but with a va_list list of + * configuration selection. + * + * Configuration value to a boolean value, where yes/true and any + * non-zero number means TRUE and other value is FALSE. + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param def_value the default value to return if no configuration + * found in the database. + * @param args a va_list of arguments + * + * @return TRUE or FALSE + * + * @ingroup heim_support + */ + +int +heim_config_vget_bool_default(heim_context context, + const heim_config_section *c, + int def_value, + va_list args) +{ + const char *str; + str = heim_config_vget_string(context, c, args); + if (str == NULL) + return def_value; + return !!(strcasecmp(str, "yes") == 0 || + strcasecmp(str, "true") == 0 || + atoi(str)); +} + +/** + * heim_config_get_bool() will convert the configuration + * option value to a boolean value, where yes/true and any non-zero + * number means TRUE and other value is FALSE. + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param args a va_list of arguments + * + * @return TRUE or FALSE + * + * @ingroup heim_support + */ + +int +heim_config_vget_bool(heim_context context, + const heim_config_section *c, + va_list args) +{ + return heim_config_vget_bool_default(context, c, 0, args); +} + +/** + * heim_config_get_bool_default() will convert the configuration + * option value to a boolean value, where yes/true and any non-zero + * number means TRUE and other value is FALSE. + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param def_value the default value to return if no configuration + * found in the database. + * @param ... a list of names, terminated with NULL. + * + * @return TRUE or FALSE + * + * @ingroup heim_support + */ + +int +heim_config_get_bool_default(heim_context context, + const heim_config_section *c, + int def_value, + ...) +{ + va_list ap; + int ret; + + va_start(ap, def_value); + ret = heim_config_vget_bool_default(context, c, def_value, ap); + va_end(ap); + return ret; +} + +/** + * Like heim_config_get_bool() but with a va_list list of + * configuration selection. + * + * Configuration value to a boolean value, where yes/true and any + * non-zero number means TRUE and other value is FALSE. + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param ... a list of names, terminated with NULL. + * + * @return TRUE or FALSE + * + * @ingroup heim_support + */ + +int +heim_config_get_bool(heim_context context, + const heim_config_section *c, + ...) +{ + va_list ap; + int ret; + va_start(ap, c); + ret = heim_config_vget_bool (context, c, ap); + va_end(ap); + return ret; +} + +/** + * Get the time from the configuration file using a relative time. + * + * Like heim_config_get_time_default() but with a va_list list of + * configuration selection. + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param def_value the default value to return if no configuration + * found in the database. + * @param args a va_list of arguments + * + * @return parsed the time (or def_value on parse error) + * + * @ingroup heim_support + */ + +time_t +heim_config_vget_time_default(heim_context context, + const heim_config_section *c, + int def_value, + va_list args) +{ + const char *str; + time_t t = -1; + + if ((str = heim_config_vget_string(context, c, args))) + t = parse_time(str, "s"); + return t != -1 ? t : def_value; +} + +/** + * Get the time from the configuration file using a relative time, for example: 1h30s + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param args a va_list of arguments + * + * @return parsed the time or -1 on error + * + * @ingroup heim_support + */ + +time_t +heim_config_vget_time(heim_context context, + const heim_config_section *c, + va_list args) +{ + return heim_config_vget_time_default(context, c, -1, args); +} + +/** + * Get the time from the configuration file using a relative time, for example: 1h30s + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param def_value the default value to return if no configuration + * found in the database. + * @param ... a list of names, terminated with NULL. + * + * @return parsed the time (or def_value on parse error) + * + * @ingroup heim_support + */ + +time_t +heim_config_get_time_default(heim_context context, + const heim_config_section *c, + int def_value, + ...) +{ + va_list ap; + time_t ret; + + va_start(ap, def_value); + ret = heim_config_vget_time_default(context, c, def_value, ap); + va_end(ap); + return ret; +} + +/** + * Get the time from the configuration file using a relative time, for example: 1h30s + * + * @param context A Kerberos 5 context. + * @param c a configuration section, or NULL to use the section from context + * @param ... a list of names, terminated with NULL. + * + * @return parsed the time or -1 on error + * + * @ingroup heim_support + */ + +time_t +heim_config_get_time(heim_context context, + const heim_config_section *c, + ...) +{ + va_list ap; + int ret; + va_start(ap, c); + ret = heim_config_vget_time(context, c, ap); + va_end(ap); + return ret; +} + + +int +heim_config_vget_int_default(heim_context context, + const heim_config_section *c, + int def_value, + va_list args) +{ + const char *str; + str = heim_config_vget_string (context, c, args); + if(str == NULL) + return def_value; + else { + char *endptr; + long l; + l = strtol(str, &endptr, 0); + if (endptr == str) + return def_value; + else + return l; + } +} + +int +heim_config_vget_int(heim_context context, + const heim_config_section *c, + va_list args) +{ + return heim_config_vget_int_default(context, c, -1, args); +} + +int +heim_config_get_int_default(heim_context context, + const heim_config_section *c, + int def_value, + ...) +{ + va_list ap; + int ret; + + va_start(ap, def_value); + ret = heim_config_vget_int_default(context, c, def_value, ap); + va_end(ap); + return ret; +} + +int +heim_config_get_int(heim_context context, + const heim_config_section *c, + ...) +{ + va_list ap; + int ret; + va_start(ap, c); + ret = heim_config_vget_int (context, c, ap); + va_end(ap); + return ret; +} + +#ifndef HEIMDAL_SMALLER +heim_error_code +heim_config_parse_string_multi(heim_context context, + const char *string, + heim_config_section **res) +{ + const char *str; + unsigned lineno = 0; + heim_error_code ret; + struct fileptr f; + + f.context = context; + f.f = NULL; + f.s = string; + + ret = heim_config_parse_debug(&f, res, &lineno, &str); + if (ret) { + if (ret != HEIM_ERR_CONFIG_BADFORMAT) { + ret = HEIM_ERR_CONFIG_BADFORMAT; + heim_set_error_message(context, ret, "%s:%u: %s", + "", lineno, str); + } + return ret; + } + return 0; +} +#endif diff --git a/third_party/heimdal/lib/base/config_reg.c b/third_party/heimdal/lib/base/config_reg.c new file mode 100644 index 0000000..cb24e50 --- /dev/null +++ b/third_party/heimdal/lib/base/config_reg.c @@ -0,0 +1,658 @@ +/*********************************************************************** + * Copyright (c) 2010, Secure Endpoints Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + **********************************************************************/ + +#include "baselocl.h" + +#ifndef _WIN32 +#error config_reg.c is only for Windows +#endif + +#include + +#ifndef MAX_DWORD +#define MAX_DWORD 0xFFFFFFFF +#endif + +/** + * Store a string as a registry value of the specified type + * + * The following registry types are handled: + * + * - REG_DWORD: The string is converted to a number. + * + * - REG_SZ: The string is stored as is. + * + * - REG_EXPAND_SZ: The string is stored as is. + * + * - REG_MULTI_SZ: + * + * . If a separator is specified, the input string is broken + * up into multiple strings and stored as a multi-sz. + * + * . If no separator is provided, the input string is stored + * as a multi-sz. + * + * - REG_NONE: + * + * . If the string is all numeric, it will be stored as a + * REG_DWORD. + * + * . Otherwise, the string is stored as a REG_SZ. + * + * Other types are rejected. + * + * If cb_data is MAX_DWORD, the string pointed to by data must be nul-terminated + * otherwise a buffer overrun will occur. + * + * @param [in]valuename Name of the registry value to be modified or created + * @param [in]type Type of the value. REG_NONE if unknown + * @param [in]data The input string to be stored in the registry. + * @param [in]cb_data Size of the input string in bytes. MAX_DWORD if unknown. + * @param [in]separator Separator character for parsing strings. + * + * @retval 0 if success or non-zero on error. + * If non-zero is returned, an error message has been set using + * heim_set_error_message(). + * + */ +int +heim_store_string_to_reg_value(heim_context context, + HKEY key, const char *valuename, + DWORD type, const char *data, DWORD cb_data, + const char *separator) +{ + LONG rcode; + int dwData; + BYTE static_buffer[16384]; + + if (data == NULL) + { + if (context) + heim_set_error_message(context, 0, + "'data' must not be NULL"); + return -1; + } + + if (cb_data == MAX_DWORD) + { + cb_data = (DWORD)strlen(data) + 1; + } + else if ((type == REG_MULTI_SZ && cb_data >= sizeof(static_buffer) - 1) || + cb_data >= sizeof(static_buffer)) + { + if (context) + heim_set_error_message(context, 0, "cb_data too big"); + return -1; + } + else if (data[cb_data-1] != '\0') + { + memcpy(static_buffer, data, cb_data); + static_buffer[cb_data++] = '\0'; + if (type == REG_MULTI_SZ) + static_buffer[cb_data++] = '\0'; + data = static_buffer; + } + + if (type == REG_NONE) + { + /* + * If input is all numeric, convert to DWORD and save as REG_DWORD. + * Otherwise, store as REG_SZ. + */ + if ( StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) ) + { + type = REG_DWORD; + } else { + type = REG_SZ; + } + } + + switch (type) { + case REG_SZ: + case REG_EXPAND_SZ: + rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data); + if (rcode) + { + if (context) + heim_set_error_message(context, 0, + "Unexpected error when setting registry value %s gle 0x%x", + valuename, + GetLastError()); + return -1; + } + break; + case REG_MULTI_SZ: + if (separator && *separator) + { + char *cp; + + if (data != static_buffer) + static_buffer[cb_data++] = '\0'; + + for ( cp = static_buffer; cp < static_buffer+cb_data; cp++) + { + if (*cp == *separator) + *cp = '\0'; + } + + rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data); + if (rcode) + { + if (context) + heim_set_error_message(context, 0, + "Unexpected error when setting registry value %s gle 0x%x", + valuename, + GetLastError()); + return -1; + } + } + break; + case REG_DWORD: + if ( !StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) ) + { + if (context) + heim_set_error_message(context, 0, + "Unexpected error when parsing %s as number gle 0x%x", + data, + GetLastError()); + } + + rcode = RegSetValueEx(key, valuename, 0, type, (BYTE *)&dwData, sizeof(DWORD)); + if (rcode) + { + if (context) + heim_set_error_message(context, 0, + "Unexpected error when setting registry value %s gle 0x%x", + valuename, + GetLastError()); + return -1; + } + break; + default: + return -1; + } + + return 0; +} + +/** + * Parse a registry value as a string + * + * @see heim_parse_reg_value_as_multi_string() + */ +char * +heim_parse_reg_value_as_string(heim_context context, + HKEY key, const char * valuename, + DWORD type, DWORD cb_data) +{ + return heim_parse_reg_value_as_multi_string(context, key, valuename, + type, cb_data, " "); +} + +/** + * Parse a registry value as a multi string + * + * The following registry value types are handled: + * + * - REG_DWORD: The decimal string representation is used as the + * value. + * + * - REG_SZ: The string is used as-is. + * + * - REG_EXPAND_SZ: Environment variables in the string are expanded + * and the result is used as the value. + * + * - REG_MULTI_SZ: The list of strings is concatenated using the + * separator. No quoting is performed. + * + * Any other value type is rejected. + * + * @param [in]valuename Name of the registry value to be queried + * @param [in]type Type of the value. REG_NONE if unknown + * @param [in]cbdata Size of value. 0 if unknown. + * @param [in]separator Separator character for concatenating strings. + * + * @a type and @a cbdata are only considered valid if both are + * specified. + * + * @retval The registry value string, or NULL if there was an error. + * If NULL is returned, an error message has been set using + * heim_set_error_message(). + */ +char * +heim_parse_reg_value_as_multi_string(heim_context context, + HKEY key, const char * valuename, + DWORD type, DWORD cb_data, char *separator) +{ + LONG rcode = ERROR_MORE_DATA; + + BYTE static_buffer[16384]; + BYTE *pbuffer = &static_buffer[0]; + DWORD cb_alloc = sizeof(static_buffer); + char *ret_string = NULL; + + /* If we know a type and cb_data from a previous call to + * RegEnumValue(), we use it. Otherwise we use the + * static_buffer[] and query directly. We do this to minimize the + * number of queries. */ + + if (type == REG_NONE || cb_data == 0) { + + pbuffer = &static_buffer[0]; + cb_alloc = cb_data = sizeof(static_buffer); + rcode = RegQueryValueExA(key, valuename, NULL, &type, pbuffer, &cb_data); + + if (rcode == ERROR_SUCCESS && + + ((type != REG_SZ && + type != REG_EXPAND_SZ) || cb_data + 1 <= sizeof(static_buffer)) && + + (type != REG_MULTI_SZ || cb_data + 2 <= sizeof(static_buffer))) + goto have_data; + + if (rcode != ERROR_MORE_DATA && rcode != ERROR_SUCCESS) + return NULL; + } + + /* Either we don't have the data or we aren't sure of the size + * (due to potentially missing terminating NULs). */ + + switch (type) { + case REG_DWORD: + if (cb_data != sizeof(DWORD)) { + if (context) + heim_set_error_message(context, 0, + "Unexpected size while reading registry value %s", + valuename); + return NULL; + } + break; + + case REG_SZ: + case REG_EXPAND_SZ: + + if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0') + goto have_data; + + cb_data += sizeof(char); /* Accout for potential missing NUL + * terminator. */ + break; + + case REG_MULTI_SZ: + + if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0' && + (cb_data == 1 || pbuffer[cb_data - 2] == '\0')) + goto have_data; + + cb_data += sizeof(char) * 2; /* Potential missing double NUL + * terminator. */ + break; + + default: + if (context) + heim_set_error_message(context, 0, + "Unexpected type while reading registry value %s", + valuename); + return NULL; + } + + if (cb_data <= sizeof(static_buffer)) + pbuffer = &static_buffer[0]; + else { + pbuffer = malloc(cb_data); + if (pbuffer == NULL) + return NULL; + } + + cb_alloc = cb_data; + rcode = RegQueryValueExA(key, valuename, NULL, NULL, pbuffer, &cb_data); + + if (rcode != ERROR_SUCCESS) { + + /* This can potentially be from a race condition. I.e. some + * other process or thread went and modified the registry + * value between the time we queried its size and queried for + * its value. Ideally we would retry the query in a loop. */ + + if (context) + heim_set_error_message(context, 0, + "Unexpected error while reading registry value %s", + valuename); + goto done; + } + + if (cb_data > cb_alloc || cb_data == 0) { + if (context) + heim_set_error_message(context, 0, + "Unexpected size while reading registry value %s", + valuename); + goto done; + } + +have_data: + switch (type) { + case REG_DWORD: + asprintf(&ret_string, "%d", *((DWORD *) pbuffer)); + break; + + case REG_SZ: + { + char * str = (char *) pbuffer; + + if (str[cb_data - 1] != '\0') { + if (cb_data < cb_alloc) + str[cb_data] = '\0'; + else + break; + } + + if (pbuffer != static_buffer) { + ret_string = (char *) pbuffer; + pbuffer = NULL; + } else { + ret_string = strdup((char *) pbuffer); + } + } + break; + + case REG_EXPAND_SZ: + { + char *str = (char *) pbuffer; + char expsz[32768]; /* Size of output buffer for + * ExpandEnvironmentStrings() is + * limited to 32K. */ + + if (str[cb_data - 1] != '\0') { + if (cb_data < cb_alloc) + str[cb_data] = '\0'; + else + break; + } + + if (ExpandEnvironmentStrings(str, expsz, sizeof(expsz)/sizeof(char)) != 0) { + ret_string = strdup(expsz); + } else { + if (context) + heim_set_error_message(context, 0, + "Overflow while expanding environment strings " + "for registry value %s", valuename); + } + } + break; + + case REG_MULTI_SZ: + { + char * str = (char *) pbuffer; + char * iter; + + str[cb_alloc - 1] = '\0'; + str[cb_alloc - 2] = '\0'; + + for (iter = str; *iter;) { + size_t len = strlen(iter); + + iter += len; + if (iter[1] != '\0') + *iter++ = *separator; + else + break; + } + + if (pbuffer != static_buffer) { + ret_string = str; + pbuffer = NULL; + } else { + ret_string = strdup(str); + } + } + break; + + default: + if (context) + heim_set_error_message(context, 0, + "Unexpected type while reading registry value %s", + valuename); + } + +done: + if (pbuffer != static_buffer && pbuffer != NULL) + free(pbuffer); + + return ret_string; +} + +/** + * Parse a registry value as a configuration value + * + * @see parse_reg_value_as_string() + */ +static heim_error_code +parse_reg_value(heim_context context, + HKEY key, const char * valuename, + DWORD type, DWORD cbdata, heim_config_section ** parent) +{ + char *reg_string = NULL; + heim_config_section *value; + heim_error_code code = 0; + + reg_string = heim_parse_reg_value_as_string(context, key, valuename, type, cbdata); + + if (reg_string == NULL) + return HEIM_ERR_CONFIG_BADFORMAT; + + value = heim_config_get_entry(parent, valuename, heim_config_string); + if (value == NULL) { + code = ENOMEM; + goto done; + } + + if (value->u.string != NULL) + free(value->u.string); + + value->u.string = reg_string; + reg_string = NULL; + +done: + if (reg_string != NULL) + free(reg_string); + + return code; +} + +static heim_error_code +parse_reg_values(heim_context context, + HKEY key, + heim_config_section ** parent) +{ + DWORD index; + LONG rcode; + + for (index = 0; ; index ++) { + char name[16385]; + DWORD cch = sizeof(name)/sizeof(name[0]); + DWORD type; + DWORD cbdata = 0; + heim_error_code code; + + rcode = RegEnumValue(key, index, name, &cch, NULL, + &type, NULL, &cbdata); + if (rcode != ERROR_SUCCESS) + break; + + if (cbdata == 0) + continue; + + code = parse_reg_value(context, key, name, type, cbdata, parent); + if (code != 0) + return code; + } + + return 0; +} + +static heim_error_code +parse_reg_subkeys(heim_context context, + HKEY key, + heim_config_section ** parent) +{ + DWORD index; + LONG rcode; + + for (index = 0; ; index ++) { + HKEY subkey = NULL; + char name[256]; + DWORD cch = sizeof(name)/sizeof(name[0]); + heim_config_section *section = NULL; + heim_error_code code; + + rcode = RegEnumKeyEx(key, index, name, &cch, NULL, NULL, NULL, NULL); + if (rcode != ERROR_SUCCESS) + break; + + rcode = RegOpenKeyEx(key, name, 0, KEY_READ, &subkey); + if (rcode != ERROR_SUCCESS) + continue; + + section = heim_config_get_entry(parent, name, heim_config_list); + if (section == NULL) { + RegCloseKey(subkey); + return ENOMEM; + } + + code = parse_reg_values(context, subkey, §ion->u.list); + if (code) { + RegCloseKey(subkey); + return code; + } + + code = parse_reg_subkeys(context, subkey, §ion->u.list); + if (code) { + RegCloseKey(subkey); + return code; + } + + RegCloseKey(subkey); + } + + return 0; +} + +static heim_error_code +parse_reg_root(heim_context context, + HKEY key, + heim_config_section ** parent) +{ + heim_config_section *libdefaults = NULL; + heim_error_code code = 0; + + libdefaults = heim_config_get_entry(parent, "libdefaults", heim_config_list); + if (libdefaults == NULL) + return heim_enomem(context); + + code = parse_reg_values(context, key, &libdefaults->u.list); + if (code) + return code; + + return parse_reg_subkeys(context, key, parent); +} + +static heim_error_code +load_config_from_regpath(heim_context context, + HKEY hk_root, + const char* key_path, + heim_config_section ** res) +{ + HKEY key = NULL; + LONG rcode; + heim_error_code code = 0; + + rcode = RegOpenKeyEx(hk_root, key_path, 0, KEY_READ, &key); + if (rcode == ERROR_SUCCESS) { + code = parse_reg_root(context, key, res); + RegCloseKey(key); + key = NULL; + } + + return code; +} + +/** + * Load configuration from registry + * + * The registry keys 'HKCU\Software\Heimdal' and + * 'HKLM\Software\Heimdal' are treated as krb5.conf files. Each + * registry key corresponds to a configuration section (or bound list) + * and each value in a registry key is treated as a bound value. The + * set of values that are directly under the Heimdal key are treated + * as if they were defined in the [libdefaults] section. + * + * @see parse_reg_value() for details about how each type of value is handled. + */ +heim_error_code +heim_load_config_from_registry(heim_context context, + const char *path0, + const char *path1, + heim_config_section **res) +{ + heim_error_code code; + + if (!path0 && !path1) + return EINVAL; + + if (path0) { + code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE, + path0, res); + if (code) + return code; + } + + if (path1) { + code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE, + path1, res); + if (code) + return code; + } + + if (path0) { + code = load_config_from_regpath(context, HKEY_CURRENT_USER, + path0, res); + if (code) + return code; + } + + if (path0) { + code = load_config_from_regpath(context, HKEY_CURRENT_USER, + path1, res); + if (code) + return code; + } + return 0; +} diff --git a/third_party/heimdal/lib/base/context.c b/third_party/heimdal/lib/base/context.c new file mode 100644 index 0000000..f22ce94 --- /dev/null +++ b/third_party/heimdal/lib/base/context.c @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2020 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" + +#undef __attribute__ +#define __attribute__(X) + +heim_context +heim_context_init(void) +{ + heim_context context; + + if ((context = calloc(1, sizeof(*context))) == NULL) + return NULL; + + context->homedir_access = !issuid(); + context->log_utc = 1; + context->error_string = NULL; + context->debug_dest = NULL; + context->warn_dest = NULL; + context->log_dest = NULL; + context->time_fmt = NULL; + context->et_list = NULL; + return context; +} + +void +heim_context_free(heim_context *contextp) +{ + heim_context context = *contextp; + + *contextp = NULL; + if (!context) + return; + heim_closelog(context, context->debug_dest); + heim_closelog(context, context->warn_dest); + heim_closelog(context, context->log_dest); + free_error_table(context->et_list); + free(context->time_fmt); + free(context->error_string); + free(context); +} + +heim_error_code +heim_add_et_list(heim_context context, void (*func)(struct et_list **)) +{ + (*func)(&context->et_list); + return 0; +} + +heim_error_code +heim_context_set_time_fmt(heim_context context, const char *fmt) +{ + char *s; + + if (fmt == NULL) { + free(context->time_fmt); + return 0; + } + if ((s = strdup(fmt)) == NULL) + return heim_enomem(context); + free(context->time_fmt); + context->time_fmt = s; + return 0; +} + +const char * +heim_context_get_time_fmt(heim_context context) +{ + return context->time_fmt ? context->time_fmt : "%Y-%m-%dT%H:%M:%S"; +} + +unsigned int +heim_context_set_log_utc(heim_context context, unsigned int log_utc) +{ + unsigned int old = context->log_utc; + + context->log_utc = log_utc ? 1 : 0; + return old; +} + +int +heim_context_get_log_utc(heim_context context) +{ + return context->log_utc; +} + +unsigned int +heim_context_set_homedir_access(heim_context context, unsigned int homedir_access) +{ + unsigned int old = context->homedir_access; + + context->homedir_access = homedir_access ? 1 : 0; + return old; +} + +unsigned int +heim_context_get_homedir_access(heim_context context) +{ + return context->homedir_access; +} + +heim_error_code +heim_enomem(heim_context context) +{ + heim_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; +} + +heim_log_facility * +heim_get_log_dest(heim_context context) +{ + return context->log_dest; +} + +heim_log_facility * +heim_get_warn_dest(heim_context context) +{ + return context->warn_dest; +} + +heim_log_facility * +heim_get_debug_dest(heim_context context) +{ + return context->debug_dest; +} + +heim_error_code +heim_set_log_dest(heim_context context, heim_log_facility *fac) +{ + context->log_dest = heim_log_ref(fac); + return 0; +} + +heim_error_code +heim_set_warn_dest(heim_context context, heim_log_facility *fac) +{ + context->warn_dest = fac; + return 0; +} + +heim_error_code +heim_set_debug_dest(heim_context context, heim_log_facility *fac) +{ + context->debug_dest = fac; + return 0; +} + +#ifndef PATH_SEP +# define PATH_SEP ":" +#endif + +static heim_error_code +add_file(char ***pfilenames, int *len, char *file) +{ + char **pp = *pfilenames; + int i; + + for(i = 0; i < *len; i++) { + if(strcmp(pp[i], file) == 0) { + free(file); + return 0; + } + } + + pp = realloc(*pfilenames, (*len + 2) * sizeof(*pp)); + if (pp == NULL) { + free(file); + return ENOMEM; + } + + pp[*len] = file; + pp[*len + 1] = NULL; + *pfilenames = pp; + *len += 1; + return 0; +} + +#ifdef WIN32 +static char * +get_default_config_config_files_from_registry(const char *envvar) +{ + static const char *KeyName = "Software\\Heimdal"; /* XXX #define this */ + const char *ValueName; + char *config_file = NULL; + LONG rcode; + HKEY key; + + if (stricmp(envvar, "KRB5_CONFIG") == 0) + ValueName = "config"; + else + ValueName = envvar; + + rcode = RegOpenKeyEx(HKEY_CURRENT_USER, KeyName, 0, KEY_READ, &key); + if (rcode == ERROR_SUCCESS) { + config_file = heim_parse_reg_value_as_multi_string(NULL, key, ValueName, + REG_NONE, 0, PATH_SEP); + RegCloseKey(key); + } + + if (config_file) + return config_file; + + rcode = RegOpenKeyEx(HKEY_LOCAL_MACHINE, KeyName, 0, KEY_READ, &key); + if (rcode == ERROR_SUCCESS) { + config_file = heim_parse_reg_value_as_multi_string(NULL, key, ValueName, + REG_NONE, 0, PATH_SEP); + RegCloseKey(key); + } + + return config_file; +} +#endif + +heim_error_code +heim_prepend_config_files(const char *filelist, + char **pq, + char ***ret_pp) +{ + heim_error_code ret; + const char *p, *q; + char **pp; + int len; + char *fn; + + pp = NULL; + + len = 0; + p = filelist; + while(1) { + ssize_t l; + q = p; + l = strsep_copy(&q, PATH_SEP, NULL, 0); + if(l == -1) + break; + fn = malloc(l + 1); + if(fn == NULL) { + heim_free_config_files(pp); + return ENOMEM; + } + (void) strsep_copy(&p, PATH_SEP, fn, l + 1); + ret = add_file(&pp, &len, fn); + if (ret) { + heim_free_config_files(pp); + return ret; + } + } + + if (pq != NULL) { + int i; + + for (i = 0; pq[i] != NULL; i++) { + fn = strdup(pq[i]); + if (fn == NULL) { + heim_free_config_files(pp); + return ENOMEM; + } + ret = add_file(&pp, &len, fn); + if (ret) { + heim_free_config_files(pp); + return ret; + } + } + } + + *ret_pp = pp; + return 0; +} + +heim_error_code +heim_prepend_config_files_default(const char *prepend, + const char *def, + const char *envvar, + char ***pfilenames) +{ + heim_error_code ret; + char **defpp, **pp = NULL; + + ret = heim_get_default_config_files(def, envvar, &defpp); + if (ret) + return ret; + + ret = heim_prepend_config_files(prepend, defpp, &pp); + heim_free_config_files(defpp); + if (ret) { + return ret; + } + *pfilenames = pp; + return 0; +} + +heim_error_code +heim_get_default_config_files(const char *def, + const char *envvar, + char ***pfilenames) +{ + const char *files = NULL; + + files = secure_getenv(envvar); + +#ifdef _WIN32 + if (files == NULL) { + char * reg_files; + reg_files = get_default_config_config_files_from_registry(envvar); + if (reg_files != NULL) { + heim_error_code code; + + code = heim_prepend_config_files(reg_files, NULL, pfilenames); + free(reg_files); + + return code; + } + } +#endif + + if (files == NULL) + files = def; + return heim_prepend_config_files(files, NULL, pfilenames); +} + +#ifdef _WIN32 +#define REGPATH_KERBEROS "SOFTWARE\\Kerberos" +#define REGPATH_HEIMDAL "SOFTWARE\\Heimdal" +#endif + +heim_error_code +heim_set_config_files(heim_context context, char **filenames, + heim_config_binding **res) +{ + heim_error_code ret = 0; + + *res = NULL; + while (filenames != NULL && *filenames != NULL && **filenames != '\0') { + ret = heim_config_parse_file_multi(context, *filenames, res); + if (ret != 0 && ret != ENOENT && ret != EACCES && ret != EPERM + && ret != HEIM_ERR_CONFIG_BADFORMAT) { + heim_config_file_free(context, *res); + *res = NULL; + return ret; + } + filenames++; + } + +#ifdef _WIN32 + /* + * We always ignored errors from loading from the registry, so we still do. + */ + heim_load_config_from_registry(context, REGPATH_KERBEROS, + REGPATH_HEIMDAL, res); + +#endif + return 0; +} + +void +heim_free_config_files(char **filenames) +{ + char **p; + + for (p = filenames; p && *p != NULL; p++) + free(*p); + free(filenames); +} diff --git a/third_party/heimdal/lib/base/data.c b/third_party/heimdal/lib/base/data.c new file mode 100644 index 0000000..cefdde0 --- /dev/null +++ b/third_party/heimdal/lib/base/data.c @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2011 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" +#include + +static void HEIM_CALLCONV +data_dealloc(void *ptr) +{ + heim_data_t d = ptr; + heim_octet_string *os = (heim_octet_string *)d; + heim_data_free_f_t *deallocp; + heim_data_free_f_t dealloc; + + if (os->data == NULL) + return; + + /* Possible string ref */ + deallocp = _heim_get_isaextra(os, 0); + dealloc = *deallocp; + if (dealloc != NULL) + dealloc(os->data); +} + +static int +data_cmp(void *a, void *b) +{ + heim_octet_string *osa = a, *osb = b; + if (osa->length != osb->length) + return osa->length - osb->length; + return memcmp(osa->data, osb->data, osa->length); +} + +static uintptr_t +data_hash(void *ptr) +{ + heim_octet_string *os = ptr; + const unsigned char *s = os->data; + + if (os->length < 4) + return os->length; + + return ((unsigned long)s[os->length - 1] << 24) + | (s[os->length - 2] << 16) | (s[1] << 8) | s[0]; +} + +struct heim_type_data _heim_data_object = { + HEIM_TID_DATA, + "data-object", + NULL, + data_dealloc, + NULL, + data_cmp, + data_hash, + NULL +}; + +/** + * Create a data object + * + * @param string the string to create, must be an utf8 string + * + * @return string object + */ + +heim_data_t +heim_data_create(const void *data, size_t length) +{ + heim_octet_string *os; + + os = _heim_alloc_object(&_heim_data_object, sizeof(*os) + length); + if (os) { + os->data = (uint8_t *)os + sizeof(*os); + os->length = length; + memcpy(os->data, data, length); + } + return (heim_data_t)os; +} + +heim_data_t +heim_data_ref_create(const void *data, size_t length, + heim_data_free_f_t dealloc) +{ + heim_octet_string *os; + heim_data_free_f_t *deallocp; + + os = _heim_alloc_object(&_heim_data_object, sizeof(*os) + length); + if (os) { + os->data = (void *)data; + os->length = length; + deallocp = _heim_get_isaextra(os, 0); + *deallocp = dealloc; + } + return (heim_data_t)os; +} + + +/** + * Return the type ID of data objects + * + * @return type id of data objects + */ + +heim_tid_t +heim_data_get_type_id(void) +{ + return HEIM_TID_DATA; +} + +/** + * Get the data value of the content. + * + * @param data the data object to get the value from + * + * @return a heim_octet_string + */ + +const heim_octet_string * +heim_data_get_data(heim_data_t data) +{ + /* Note that this works for data and data_ref objects */ + return (const heim_octet_string *)data; +} + +const void * +heim_data_get_ptr(heim_data_t data) +{ + /* Note that this works for data and data_ref objects */ + return ((const heim_octet_string *)data)->data; +} + +size_t heim_data_get_length(heim_data_t data) +{ + /* Note that this works for data and data_ref objects */ + return ((const heim_octet_string *)data)->length; +} diff --git a/third_party/heimdal/lib/base/db.c b/third_party/heimdal/lib/base/db.c new file mode 100644 index 0000000..e6f6af4 --- /dev/null +++ b/third_party/heimdal/lib/base/db.c @@ -0,0 +1,1724 @@ +/* + * Copyright (c) 2011, Secure Endpoints Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This is a pluggable simple DB abstraction, with a simple get/set/ + * delete key/value pair interface. + * + * Plugins may provide any of the following optional features: + * + * - tables -- multiple attribute/value tables in one DB + * - locking + * - transactions (i.e., allow any heim_object_t as key or value) + * - transcoding of values + * + * Stackable plugins that provide missing optional features are + * possible. + * + * Any plugin that provides locking will also provide transactions, but + * those transactions will not be atomic in the face of failures (a + * memory-based rollback log is used). + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef WIN32 +#include +#else +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include "baselocl.h" +#include + +#define HEIM_ENOMEM(ep) \ + (((ep) && !*(ep)) ? \ + heim_error_get_code((*(ep) = heim_error_create_enomem())) : ENOMEM) + +#define HEIM_ERROR_HELPER(ep, ec, args) \ + (((ep) && !*(ep)) ? \ + heim_error_get_code((*(ep) = heim_error_create args)) : (ec)) + +#define HEIM_ERROR(ep, ec, args) \ + (ec == ENOMEM) ? HEIM_ENOMEM(ep) : HEIM_ERROR_HELPER(ep, ec, args); + +static heim_string_t to_base64(heim_data_t, heim_error_t *); +static heim_data_t from_base64(heim_string_t, heim_error_t *); + +static int open_file(const char *, int , int, int *, heim_error_t *); +static int read_json(const char *, heim_object_t *, heim_error_t *); +static struct heim_db_type json_dbt; + +static void HEIM_CALLCONV db_dealloc(void *ptr); + +struct heim_type_data db_object = { + HEIM_TID_DB, + "db-object", + NULL, + db_dealloc, + NULL, + NULL, + NULL, + NULL +}; + + +static heim_base_once_t db_plugin_init_once = HEIM_BASE_ONCE_INIT; + +static heim_dict_t db_plugins; + +typedef struct db_plugin { + heim_string_t name; + heim_db_plug_open_f_t openf; + heim_db_plug_clone_f_t clonef; + heim_db_plug_close_f_t closef; + heim_db_plug_lock_f_t lockf; + heim_db_plug_unlock_f_t unlockf; + heim_db_plug_sync_f_t syncf; + heim_db_plug_begin_f_t beginf; + heim_db_plug_commit_f_t commitf; + heim_db_plug_rollback_f_t rollbackf; + heim_db_plug_copy_value_f_t copyf; + heim_db_plug_set_value_f_t setf; + heim_db_plug_del_key_f_t delf; + heim_db_plug_iter_f_t iterf; + void *data; +} db_plugin_desc, *db_plugin; + +struct heim_db_data { + db_plugin plug; + heim_string_t dbtype; + heim_string_t dbname; + heim_dict_t options; + void *db_data; + heim_data_t to_release; + heim_error_t error; + int ret; + unsigned int in_transaction:1; + unsigned int ro:1; + unsigned int ro_tx:1; + heim_dict_t set_keys; + heim_dict_t del_keys; + heim_string_t current_table; +}; + +static int +db_do_log_actions(heim_db_t db, heim_error_t *error); +static int +db_replay_log(heim_db_t db, heim_error_t *error); + +static HEIMDAL_MUTEX db_type_mutex = HEIMDAL_MUTEX_INITIALIZER; + +static void +db_init_plugins_once(void *arg) +{ + db_plugins = heim_retain(arg); +} + +static void HEIM_CALLCONV +plugin_dealloc(void *arg) +{ + db_plugin plug = arg; + + heim_release(plug->name); +} + +/** heim_db_register + * @brief Registers a DB type for use with heim_db_create(). + * + * @param dbtype Name of DB type + * @param data Private data argument to the dbtype's openf method + * @param plugin Structure with DB type methods (function pointers) + * + * Backends that provide begin/commit/rollback methods must provide ACID + * semantics. + * + * The registered DB type will have ACID semantics for backends that do + * not provide begin/commit/rollback methods but do provide lock/unlock + * and rdjournal/wrjournal methods (using a replay log journalling + * scheme). + * + * If the registered DB type does not natively provide read vs. write + * transaction isolation but does provide a lock method then the DB will + * provide read/write transaction isolation. + * + * @return ENOMEM on failure, else 0. + * + * @addtogroup heimbase + */ +int +heim_db_register(const char *dbtype, + void *data, + struct heim_db_type *plugin) +{ + heim_dict_t plugins; + heim_string_t s; + db_plugin plug, plug2; + int ret = 0; + + if ((plugin->beginf != NULL && plugin->commitf == NULL) || + (plugin->beginf != NULL && plugin->rollbackf == NULL) || + (plugin->lockf != NULL && plugin->unlockf == NULL) || + plugin->copyf == NULL) + heim_abort("Invalid DB plugin; make sure methods are paired"); + + /* Initialize */ + plugins = heim_dict_create(11); + if (plugins == NULL) + return ENOMEM; + heim_base_once_f(&db_plugin_init_once, plugins, db_init_plugins_once); + heim_release(plugins); + heim_assert(db_plugins != NULL, "heim_db plugin table initialized"); + + s = heim_string_create(dbtype); + if (s == NULL) + return ENOMEM; + + plug = heim_alloc(sizeof (*plug), "db_plug", plugin_dealloc); + if (plug == NULL) { + heim_release(s); + return ENOMEM; + } + + plug->name = heim_retain(s); + plug->openf = plugin->openf; + plug->clonef = plugin->clonef; + plug->closef = plugin->closef; + plug->lockf = plugin->lockf; + plug->unlockf = plugin->unlockf; + plug->syncf = plugin->syncf; + plug->beginf = plugin->beginf; + plug->commitf = plugin->commitf; + plug->rollbackf = plugin->rollbackf; + plug->copyf = plugin->copyf; + plug->setf = plugin->setf; + plug->delf = plugin->delf; + plug->iterf = plugin->iterf; + plug->data = data; + + HEIMDAL_MUTEX_lock(&db_type_mutex); + plug2 = heim_dict_get_value(db_plugins, s); + if (plug2 == NULL) + ret = heim_dict_set_value(db_plugins, s, plug); + HEIMDAL_MUTEX_unlock(&db_type_mutex); + heim_release(plug); + heim_release(s); + + return ret; +} + +static void HEIM_CALLCONV +db_dealloc(void *arg) +{ + heim_db_t db = arg; + heim_assert(!db->in_transaction, + "rollback or commit heim_db_t before releasing it"); + if (db->db_data) + (void) db->plug->closef(db->db_data, NULL); + heim_release(db->to_release); + heim_release(db->dbtype); + heim_release(db->dbname); + heim_release(db->options); + heim_release(db->set_keys); + heim_release(db->del_keys); + heim_release(db->error); +} + +struct dbtype_iter { + heim_db_t db; + const char *dbname; + heim_dict_t options; + heim_error_t *error; +}; + +/* + * Helper to create a DB handle with the first registered DB type that + * can open the given DB. This is useful when the app doesn't know the + * DB type a priori. This assumes that DB types can "taste" DBs, either + * from the filename extension or from the actual file contents. + */ +static void +dbtype_iter2create_f(heim_object_t dbtype, heim_object_t junk, void *arg) +{ + struct dbtype_iter *iter_ctx = arg; + + if (iter_ctx->db != NULL) + return; + iter_ctx->db = heim_db_create(heim_string_get_utf8(dbtype), + iter_ctx->dbname, iter_ctx->options, + iter_ctx->error); +} + +/** + * Open a database of the given dbtype. + * + * Database type names can be composed of one or more pseudo-DB types + * and one concrete DB type joined with a '+' between each. For + * example: "transaction+bdb" might be a Berkeley DB with a layer above + * that provides transactions. + * + * Options may be provided via a dict (an associative array). Existing + * options include: + * + * - "create", with any value (create if DB doesn't exist) + * - "exclusive", with any value (exclusive create) + * - "truncate", with any value (truncate the DB) + * - "read-only", with any value (disallow writes) + * - "sync", with any value (make transactions durable) + * - "journal-name", with a string value naming a journal file name + * + * @param dbtype Name of DB type + * @param dbname Name of DB (likely a file path) + * @param options Options dict + * @param db Output open DB handle + * @param error Output error object + * + * @return a DB handle + * + * @addtogroup heimbase + */ +heim_db_t +heim_db_create(const char *dbtype, const char *dbname, + heim_dict_t options, heim_error_t *error) +{ + heim_string_t s; + char *p; + db_plugin plug; + heim_db_t db; + int ret = 0; + + if (options == NULL) { + options = heim_dict_create(11); + if (options == NULL) { + if (error) + *error = heim_error_create_enomem(); + return NULL; + } + } else { + (void) heim_retain(options); + } + + if (db_plugins == NULL) { + heim_release(options); + return NULL; + } + + if (dbtype == NULL || *dbtype == '\0') { + struct dbtype_iter iter_ctx = { NULL, dbname, options, error}; + + /* Try all dbtypes */ + heim_dict_iterate_f(db_plugins, &iter_ctx, dbtype_iter2create_f); + heim_release(options); + return iter_ctx.db; + } else if (strstr(dbtype, "json")) { + (void) heim_db_register(dbtype, NULL, &json_dbt); + } + + /* + * Allow for dbtypes that are composed from pseudo-dbtypes chained + * to a real DB type with '+'. For example a pseudo-dbtype might + * add locking, transactions, transcoding of values, ... + */ + p = strchr(dbtype, '+'); + if (p != NULL) + s = heim_string_create_with_bytes(dbtype, p - dbtype); + else + s = heim_string_create(dbtype); + if (s == NULL) { + heim_release(options); + return NULL; + } + + HEIMDAL_MUTEX_lock(&db_type_mutex); + plug = heim_dict_get_value(db_plugins, s); + HEIMDAL_MUTEX_unlock(&db_type_mutex); + heim_release(s); + if (plug == NULL) { + if (error) + *error = heim_error_create(ENOENT, + N_("Heimdal DB plugin not found: %s", ""), + dbtype); + heim_release(options); + return NULL; + } + + db = _heim_alloc_object(&db_object, sizeof(*db)); + if (db == NULL) { + heim_release(options); + return NULL; + } + + db->in_transaction = 0; + db->ro_tx = 0; + db->set_keys = NULL; + db->del_keys = NULL; + db->plug = plug; + db->options = options; + + ret = plug->openf(plug->data, dbtype, dbname, options, &db->db_data, error); + if (ret) { + heim_release(db); + if (error && *error == NULL) + *error = heim_error_create(ENOENT, + N_("Heimdal DB could not be opened: %s", ""), + dbname); + return NULL; + } + + ret = db_replay_log(db, error); + if (ret) { + heim_release(db); + return NULL; + } + + if (plug->clonef == NULL) { + db->dbtype = heim_string_create(dbtype); + db->dbname = heim_string_create(dbname); + + if (!db->dbtype || ! db->dbname) { + heim_release(db); + if (error) + *error = heim_error_create_enomem(); + return NULL; + } + } + + return db; +} + +/** + * Clone (duplicate) an open DB handle. + * + * This is useful for multi-threaded applications. Applications must + * synchronize access to any given DB handle. + * + * Returns EBUSY if there is an open transaction for the input db. + * + * @param db Open DB handle + * @param error Output error object + * + * @return a DB handle + * + * @addtogroup heimbase + */ +heim_db_t +heim_db_clone(heim_db_t db, heim_error_t *error) +{ + heim_db_t result; + int ret; + + if (heim_get_tid(db) != HEIM_TID_DB) + heim_abort("Expected a database"); + if (db->in_transaction) + heim_abort("DB handle is busy"); + + if (db->plug->clonef == NULL) { + return heim_db_create(heim_string_get_utf8(db->dbtype), + heim_string_get_utf8(db->dbname), + db->options, error); + } + + result = _heim_alloc_object(&db_object, sizeof(*result)); + if (result == NULL) { + if (error) + *error = heim_error_create_enomem(); + return NULL; + } + + result->set_keys = NULL; + result->del_keys = NULL; + ret = db->plug->clonef(db->db_data, &result->db_data, error); + if (ret) { + heim_release(result); + if (error && !*error) + *error = heim_error_create(ENOENT, + N_("Could not re-open DB while cloning", "")); + return NULL; + } + db->db_data = NULL; + return result; +} + +/** + * Open a transaction on the given db. + * + * @param db Open DB handle + * @param error Output error object + * + * @return 0 on success, system error otherwise + * + * @addtogroup heimbase + */ +int +heim_db_begin(heim_db_t db, int read_only, heim_error_t *error) +{ + int ret; + + if (heim_get_tid(db) != HEIM_TID_DB) + return EINVAL; + + if (db->in_transaction && (read_only || !db->ro_tx || (!read_only && !db->ro_tx))) + heim_abort("DB already in transaction"); + + if (db->plug->setf == NULL || db->plug->delf == NULL) + return EINVAL; + + if (db->plug->beginf) { + ret = db->plug->beginf(db->db_data, read_only, error); + if (ret) + return ret; + } else if (!db->in_transaction) { + /* Try to emulate transactions */ + + if (db->plug->lockf == NULL) + return EINVAL; /* can't lock? -> no transactions */ + + /* Assume unlock provides sync/durability */ + ret = db->plug->lockf(db->db_data, read_only, error); + if (ret) + return ret; + + ret = db_replay_log(db, error); + if (ret) { + ret = db->plug->unlockf(db->db_data, error); + return ret; + } + + db->set_keys = heim_dict_create(11); + if (db->set_keys == NULL) + return ENOMEM; + db->del_keys = heim_dict_create(11); + if (db->del_keys == NULL) { + heim_release(db->set_keys); + db->set_keys = NULL; + return ENOMEM; + } + } else { + heim_assert(read_only == 0, "Internal error"); + ret = db->plug->lockf(db->db_data, 0, error); + if (ret) + return ret; + } + db->in_transaction = 1; + db->ro_tx = !!read_only; + return 0; +} + +/** + * Commit an open transaction on the given db. + * + * @param db Open DB handle + * @param error Output error object + * + * @return 0 on success, system error otherwise + * + * @addtogroup heimbase + */ +int +heim_db_commit(heim_db_t db, heim_error_t *error) +{ + int ret, ret2; + heim_string_t journal_fname = NULL; + + if (heim_get_tid(db) != HEIM_TID_DB) + return EINVAL; + if (!db->in_transaction) + return 0; + if (db->plug->commitf == NULL && db->plug->lockf == NULL) + return EINVAL; + + if (db->plug->commitf != NULL) { + ret = db->plug->commitf(db->db_data, error); + if (ret) + (void) db->plug->rollbackf(db->db_data, error); + + db->in_transaction = 0; + db->ro_tx = 0; + return ret; + } + + if (db->ro_tx) { + ret = 0; + goto done; + } + + if (db->options) + journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename")); + + if (journal_fname != NULL) { + heim_array_t a; + heim_string_t journal_contents; + size_t len, bytes; + int save_errno; + + /* Create contents for replay log */ + ret = ENOMEM; + a = heim_array_create(); + if (a == NULL) + goto err; + ret = heim_array_append_value(a, db->set_keys); + if (ret) { + heim_release(a); + goto err; + } + ret = heim_array_append_value(a, db->del_keys); + if (ret) { + heim_release(a); + goto err; + } + journal_contents = heim_json_copy_serialize(a, 0, error); + heim_release(a); + + /* Write replay log */ + if (journal_fname != NULL) { + int fd; + + ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error); + if (ret) { + heim_release(journal_contents); + goto err; + } + len = strlen(heim_string_get_utf8(journal_contents)); + bytes = write(fd, heim_string_get_utf8(journal_contents), len); + save_errno = errno; + heim_release(journal_contents); + ret = close(fd); + if (bytes != len) { + /* Truncate replay log */ + (void) open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error); + ret = save_errno; + goto err; + } + if (ret) + goto err; + } + } + + /* Apply logged actions */ + ret = db_do_log_actions(db, error); + if (ret) + return ret; + + if (db->plug->syncf != NULL) { + /* fsync() or whatever */ + ret = db->plug->syncf(db->db_data, error); + if (ret) + return ret; + } + + /* Truncate replay log and we're done */ + if (journal_fname != NULL) { + int fd; + + ret2 = open_file(heim_string_get_utf8(journal_fname), 1, 0, &fd, error); + if (ret2 == 0) + (void) close(fd); + } + + /* + * Clean up; if we failed to remore the replay log that's OK, we'll + * handle that again in heim_db_commit() + */ +done: + heim_release(db->set_keys); + heim_release(db->del_keys); + db->set_keys = NULL; + db->del_keys = NULL; + db->in_transaction = 0; + db->ro_tx = 0; + + ret2 = db->plug->unlockf(db->db_data, error); + if (ret == 0) + ret = ret2; + + return ret; + +err: + return HEIM_ERROR(error, ret, + (ret, N_("Error while committing transaction: %s", ""), + strerror(ret))); +} + +/** + * Rollback an open transaction on the given db. + * + * @param db Open DB handle + * @param error Output error object + * + * @return 0 on success, system error otherwise + * + * @addtogroup heimbase + */ +int +heim_db_rollback(heim_db_t db, heim_error_t *error) +{ + int ret = 0; + + if (heim_get_tid(db) != HEIM_TID_DB) + return EINVAL; + if (!db->in_transaction) + return 0; + + if (db->plug->rollbackf != NULL) + ret = db->plug->rollbackf(db->db_data, error); + else if (db->plug->unlockf != NULL) + ret = db->plug->unlockf(db->db_data, error); + + heim_release(db->set_keys); + heim_release(db->del_keys); + db->set_keys = NULL; + db->del_keys = NULL; + db->in_transaction = 0; + db->ro_tx = 0; + + return ret; +} + +/** + * Get type ID of heim_db_t objects. + * + * @addtogroup heimbase + */ +heim_tid_t +heim_db_get_type_id(void) +{ + return HEIM_TID_DB; +} + +heim_data_t +_heim_db_get_value(heim_db_t db, heim_string_t table, heim_data_t key, + heim_error_t *error) +{ + heim_release(db->to_release); + db->to_release = heim_db_copy_value(db, table, key, error); + return db->to_release; +} + +/** + * Lookup a key's value in the DB. + * + * Returns 0 on success, -1 if the key does not exist in the DB, or a + * system error number on failure. + * + * @param db Open DB handle + * @param key Key + * @param error Output error object + * + * @return the value (retained), if there is one for the given key + * + * @addtogroup heimbase + */ +heim_data_t +heim_db_copy_value(heim_db_t db, heim_string_t table, heim_data_t key, + heim_error_t *error) +{ + heim_object_t v; + heim_data_t result; + + if (heim_get_tid(db) != HEIM_TID_DB) + return NULL; + + if (error != NULL) + *error = NULL; + + if (table == NULL) + table = HSTR(""); + + if (db->in_transaction) { + heim_string_t key64; + + key64 = to_base64(key, error); + if (key64 == NULL) { + if (error) + *error = heim_error_create_enomem(); + return NULL; + } + + v = heim_path_copy(db->set_keys, error, table, key64, NULL); + if (v != NULL) { + heim_release(key64); + return v; + } + v = heim_path_copy(db->del_keys, error, table, key64, NULL); /* can't be NULL */ + heim_release(key64); + if (v != NULL) + return NULL; + } + + result = db->plug->copyf(db->db_data, table, key, error); + + return result; +} + +/** + * Set a key's value in the DB. + * + * @param db Open DB handle + * @param key Key + * @param value Value (if NULL the key will be deleted, but empty is OK) + * @param error Output error object + * + * @return 0 on success, system error otherwise + * + * @addtogroup heimbase + */ +int +heim_db_set_value(heim_db_t db, heim_string_t table, + heim_data_t key, heim_data_t value, heim_error_t *error) +{ + heim_string_t key64 = NULL; + int ret; + + if (error != NULL) + *error = NULL; + + if (table == NULL) + table = HSTR(""); + + if (value == NULL) + /* Use heim_null_t instead of NULL */ + return heim_db_delete_key(db, table, key, error); + + if (heim_get_tid(db) != HEIM_TID_DB) + return EINVAL; + + if (heim_get_tid(key) != HEIM_TID_DATA) + return HEIM_ERROR(error, EINVAL, + (EINVAL, N_("DB keys must be data", ""))); + + if (db->plug->setf == NULL) + return EBADF; + + if (!db->in_transaction) { + ret = heim_db_begin(db, 0, error); + if (ret) + goto err; + heim_assert(db->in_transaction, "Internal error"); + ret = heim_db_set_value(db, table, key, value, error); + if (ret) { + (void) heim_db_rollback(db, NULL); + return ret; + } + return heim_db_commit(db, error); + } + + /* Transaction emulation */ + heim_assert(db->set_keys != NULL, "Internal error"); + key64 = to_base64(key, error); + if (key64 == NULL) + return HEIM_ENOMEM(error); + + if (db->ro_tx) { + ret = heim_db_begin(db, 0, error); + if (ret) + goto err; + } + ret = heim_path_create(db->set_keys, 29, value, error, table, key64, NULL); + if (ret) + goto err; + heim_path_delete(db->del_keys, error, table, key64, NULL); + heim_release(key64); + + return 0; + +err: + heim_release(key64); + return HEIM_ERROR(error, ret, + (ret, N_("Could not set a dict value while while " + "setting a DB value", ""))); +} + +/** + * Delete a key and its value from the DB + * + * + * @param db Open DB handle + * @param key Key + * @param error Output error object + * + * @return 0 on success, system error otherwise + * + * @addtogroup heimbase + */ +int +heim_db_delete_key(heim_db_t db, heim_string_t table, heim_data_t key, + heim_error_t *error) +{ + heim_string_t key64 = NULL; + int ret; + + if (error != NULL) + *error = NULL; + + if (table == NULL) + table = HSTR(""); + + if (heim_get_tid(db) != HEIM_TID_DB) + return EINVAL; + + if (db->plug->delf == NULL) + return EBADF; + + if (!db->in_transaction) { + ret = heim_db_begin(db, 0, error); + if (ret) + goto err; + heim_assert(db->in_transaction, "Internal error"); + ret = heim_db_delete_key(db, table, key, error); + if (ret) { + (void) heim_db_rollback(db, NULL); + return ret; + } + return heim_db_commit(db, error); + } + + /* Transaction emulation */ + heim_assert(db->set_keys != NULL, "Internal error"); + key64 = to_base64(key, error); + if (key64 == NULL) + return HEIM_ENOMEM(error); + if (db->ro_tx) { + ret = heim_db_begin(db, 0, error); + if (ret) + goto err; + } + ret = heim_path_create(db->del_keys, 29, heim_number_create(1), error, table, key64, NULL); + if (ret) + goto err; + heim_path_delete(db->set_keys, error, table, key64, NULL); + heim_release(key64); + + return 0; + +err: + heim_release(key64); + return HEIM_ERROR(error, ret, + (ret, N_("Could not set a dict value while while " + "deleting a DB value", ""))); +} + +/** + * Iterate a callback function over keys and values from a DB. + * + * @param db Open DB handle + * @param iter_data Callback function's private data + * @param iter_f Callback function, called once per-key/value pair + * @param error Output error object + * + * @addtogroup heimbase + */ +void +heim_db_iterate_f(heim_db_t db, heim_string_t table, void *iter_data, + heim_db_iterator_f_t iter_f, heim_error_t *error) +{ + if (error != NULL) + *error = NULL; + + if (heim_get_tid(db) != HEIM_TID_DB) + return; + + if (!db->in_transaction) + db->plug->iterf(db->db_data, table, iter_data, iter_f, error); +} + +static void +db_replay_log_table_set_keys_iter(heim_object_t key, heim_object_t value, + void *arg) +{ + heim_db_t db = arg; + heim_data_t k, v; + + if (db->ret) + return; + + k = from_base64((heim_string_t)key, &db->error); + if (k == NULL) { + db->ret = ENOMEM; + return; + } + v = (heim_data_t)value; + + db->ret = db->plug->setf(db->db_data, db->current_table, k, v, &db->error); + heim_release(k); +} + +static void +db_replay_log_table_del_keys_iter(heim_object_t key, heim_object_t value, + void *arg) +{ + heim_db_t db = arg; + heim_data_t k; + + if (db->ret) { + db->ret = ENOMEM; + return; + } + + k = from_base64((heim_string_t)key, &db->error); + if (k == NULL) + return; + + db->ret = db->plug->delf(db->db_data, db->current_table, k, &db->error); + heim_release(k); +} + +static void +db_replay_log_set_keys_iter(heim_object_t table, heim_object_t table_dict, + void *arg) +{ + heim_db_t db = arg; + + if (db->ret) + return; + + db->current_table = table; + heim_dict_iterate_f(table_dict, db, db_replay_log_table_set_keys_iter); +} + +static void +db_replay_log_del_keys_iter(heim_object_t table, heim_object_t table_dict, + void *arg) +{ + heim_db_t db = arg; + + if (db->ret) + return; + + db->current_table = table; + heim_dict_iterate_f(table_dict, db, db_replay_log_table_del_keys_iter); +} + +static int +db_do_log_actions(heim_db_t db, heim_error_t *error) +{ + int ret; + + if (error) + *error = NULL; + + db->ret = 0; + db->error = NULL; + if (db->set_keys != NULL) + heim_dict_iterate_f(db->set_keys, db, db_replay_log_set_keys_iter); + if (db->del_keys != NULL) + heim_dict_iterate_f(db->del_keys, db, db_replay_log_del_keys_iter); + + ret = db->ret; + db->ret = 0; + if (error && db->error) { + *error = db->error; + db->error = NULL; + } else { + heim_release(db->error); + db->error = NULL; + } + return ret; +} + +static int +db_replay_log(heim_db_t db, heim_error_t *error) +{ + int ret; + heim_string_t journal_fname = NULL; + heim_object_t journal; + size_t len; + + heim_assert(!db->in_transaction, "DB transaction not open"); + heim_assert(db->set_keys == NULL && db->set_keys == NULL, "DB transaction not open"); + + if (error) + *error = NULL; + + if (db->options == NULL) + return 0; + + journal_fname = heim_dict_get_value(db->options, HSTR("journal-filename")); + if (journal_fname == NULL) + return 0; + + ret = read_json(heim_string_get_utf8(journal_fname), &journal, error); + if (ret == ENOENT) { + heim_release(journal_fname); + return 0; + } + if (ret == 0 && journal == NULL) { + heim_release(journal_fname); + return 0; + } + if (ret != 0) { + heim_release(journal_fname); + return ret; + } + + if (heim_get_tid(journal) != HEIM_TID_ARRAY) { + heim_release(journal_fname); + return HEIM_ERROR(error, EINVAL, + (ret, N_("Invalid journal contents; delete journal", + ""))); + } + + len = heim_array_get_length(journal); + + if (len > 0) + db->set_keys = heim_array_get_value(journal, 0); + if (len > 1) + db->del_keys = heim_array_get_value(journal, 1); + ret = db_do_log_actions(db, error); + if (ret) { + heim_release(journal_fname); + return ret; + } + + /* Truncate replay log and we're done */ + ret = open_file(heim_string_get_utf8(journal_fname), 1, 0, NULL, error); + heim_release(journal_fname); + if (ret) + return ret; + heim_release(db->set_keys); + heim_release(db->del_keys); + db->set_keys = NULL; + db->del_keys = NULL; + + return 0; +} + +static +heim_string_t to_base64(heim_data_t data, heim_error_t *error) +{ + char *b64 = NULL; + heim_string_t s = NULL; + const heim_octet_string *d; + int ret; + + d = heim_data_get_data(data); + ret = rk_base64_encode(d->data, d->length, &b64); + if (ret < 0 || b64 == NULL) + goto enomem; + s = heim_string_ref_create(b64, free); + if (s == NULL) + goto enomem; + return s; + +enomem: + free(b64); + if (error) + *error = heim_error_create_enomem(); + return NULL; +} + +static +heim_data_t from_base64(heim_string_t s, heim_error_t *error) +{ + ssize_t len = -1; + void *buf; + heim_data_t d; + + buf = malloc(strlen(heim_string_get_utf8(s))); + if (buf) + len = rk_base64_decode(heim_string_get_utf8(s), buf); + if (len > -1 && (d = heim_data_ref_create(buf, len, free))) + return d; + free(buf); + if (error) + *error = heim_error_create_enomem(); + return NULL; +} + + +static int +open_file(const char *dbname, int for_write, int excl, int *fd_out, heim_error_t *error) +{ +#ifdef WIN32 + HANDLE hFile; + int ret = 0; + + if (fd_out) + *fd_out = -1; + + if (for_write) + hFile = CreateFile(dbname, GENERIC_WRITE | GENERIC_READ, 0, + NULL, /* we'll close as soon as we read */ + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + else + hFile = CreateFile(dbname, GENERIC_READ, FILE_SHARE_READ, + NULL, /* we'll close as soon as we read */ + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + ret = GetLastError(); + _set_errno(ret); /* CreateFile() does not set errno */ + goto err; + } + if (fd_out == NULL) { + (void) CloseHandle(hFile); + return 0; + } + + *fd_out = _open_osfhandle((intptr_t) hFile, 0); + if (*fd_out < 0) { + ret = errno; + (void) CloseHandle(hFile); + goto err; + } + + /* No need to lock given share deny mode */ + return 0; + +err: + if (error != NULL) { + char *s = NULL; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, + 0, ret, 0, (LPTSTR) &s, 0, NULL); + *error = heim_error_create(ret, N_("Could not open JSON file %s: %s", ""), + dbname, s ? s : ""); + LocalFree(s); + } + return ret; +#else + int ret = 0; + int fd; + + if (fd_out) + *fd_out = -1; + + if (for_write && excl) + fd = open(dbname, O_CREAT | O_EXCL | O_WRONLY, 0600); + else if (for_write) + fd = open(dbname, O_CREAT | O_TRUNC | O_WRONLY, 0600); + else + fd = open(dbname, O_RDONLY); + if (fd < 0) { + if (error != NULL) + *error = heim_error_create(ret, N_("Could not open JSON file %s: %s", ""), + dbname, strerror(errno)); + return errno; + } + + if (fd_out == NULL) { + (void) close(fd); + return 0; + } + + ret = flock(fd, for_write ? LOCK_EX : LOCK_SH); + if (ret == -1) { + /* Note that we if O_EXCL we're leaving the [lock] file around */ + (void) close(fd); + return HEIM_ERROR(error, errno, + (errno, N_("Could not lock JSON file %s: %s", ""), + dbname, strerror(errno))); + } + + *fd_out = fd; + + return 0; +#endif +} + +static int +read_json(const char *dbname, heim_object_t *out, heim_error_t *error) +{ + struct stat st; + char *str = NULL; + int ret; + int fd = -1; + ssize_t bytes; + + *out = NULL; + ret = open_file(dbname, 0, 0, &fd, error); + if (ret) + return ret; + + ret = fstat(fd, &st); + if (ret == -1) { + (void) close(fd); + return HEIM_ERROR(error, errno, + (ret, N_("Could not stat JSON DB %s: %s", ""), + dbname, strerror(errno))); + } + + if (st.st_size == 0) { + (void) close(fd); + return 0; + } + + str = malloc(st.st_size + 1); + if (str == NULL) { + (void) close(fd); + return HEIM_ENOMEM(error); + } + + bytes = read(fd, str, st.st_size); + (void) close(fd); + if (bytes != st.st_size) { + free(str); + if (bytes >= 0) + errno = EINVAL; /* ?? */ + return HEIM_ERROR(error, errno, + (ret, N_("Could not read JSON DB %s: %s", ""), + dbname, strerror(errno))); + } + str[st.st_size] = '\0'; + *out = heim_json_create(str, 10, 0, error); + free(str); + if (*out == NULL) + return (error && *error) ? heim_error_get_code(*error) : EINVAL; + return 0; +} + +typedef struct json_db { + heim_dict_t dict; + heim_string_t dbname; + heim_string_t bkpname; + int fd; + time_t last_read_time; + unsigned int read_only:1; + unsigned int locked:1; + unsigned int locked_needs_unlink:1; +} *json_db_t; + +static int +json_db_open(void *plug, const char *dbtype, const char *dbname, + heim_dict_t options, void **db, heim_error_t *error) +{ + json_db_t jsondb; + heim_dict_t contents = NULL; + heim_string_t dbname_s = NULL; + heim_string_t bkpname_s = NULL; + + if (error) + *error = NULL; + if (dbtype && *dbtype && strcmp(dbtype, "json") != 0) + return HEIM_ERROR(error, EINVAL, (EINVAL, N_("Wrong DB type", ""))); + if (dbname && *dbname && strcmp(dbname, "MEMORY") != 0) { + char *ext = strrchr(dbname, '.'); + char *bkpname; + size_t len; + int ret; + + if (ext == NULL || strcmp(ext, ".json") != 0) + return HEIM_ERROR(error, EINVAL, + (EINVAL, N_("JSON DB files must end in .json", + ""))); + + if (options) { + heim_object_t vc, ve, vt; + + vc = heim_dict_get_value(options, HSTR("create")); + ve = heim_dict_get_value(options, HSTR("exclusive")); + vt = heim_dict_get_value(options, HSTR("truncate")); + if (vc && vt) { + ret = open_file(dbname, 1, ve ? 1 : 0, NULL, error); + if (ret) + return ret; + } else if (vc || ve || vt) { + return HEIM_ERROR(error, EINVAL, + (EINVAL, N_("Invalid JSON DB open options", + ""))); + } + /* + * We don't want cloned handles to truncate the DB, eh? + * + * We should really just create a copy of the options dict + * rather than modify the caller's! But for that it'd be + * nicer to have copy utilities in heimbase, something like + * this: + * + * heim_object_t heim_copy(heim_object_t src, int depth, + * heim_error_t *error); + * + * so that options = heim_copy(options, 1); means copy the + * dict but nothing else (whereas depth == 0 would mean + * heim_retain(), and depth > 1 would be copy that many + * levels). + */ + heim_dict_delete_key(options, HSTR("create")); + heim_dict_delete_key(options, HSTR("exclusive")); + heim_dict_delete_key(options, HSTR("truncate")); + } + dbname_s = heim_string_create(dbname); + if (dbname_s == NULL) + return HEIM_ENOMEM(error); + + len = snprintf(NULL, 0, "%s~", dbname); + bkpname = malloc(len + 2); + if (bkpname == NULL) { + heim_release(dbname_s); + return HEIM_ENOMEM(error); + } + (void) snprintf(bkpname, len + 1, "%s~", dbname); + bkpname_s = heim_string_create(bkpname); + free(bkpname); + if (bkpname_s == NULL) { + heim_release(dbname_s); + return HEIM_ENOMEM(error); + } + + ret = read_json(dbname, (heim_object_t *)&contents, error); + if (ret) { + heim_release(bkpname_s); + heim_release(dbname_s); + return ret; + } + + if (contents != NULL && heim_get_tid(contents) != HEIM_TID_DICT) { + heim_release(bkpname_s); + heim_release(dbname_s); + return HEIM_ERROR(error, EINVAL, + (EINVAL, N_("JSON DB contents not valid JSON", + ""))); + } + } + + jsondb = heim_alloc(sizeof (*jsondb), "json_db", NULL); + if (jsondb == NULL) { + heim_release(contents); + heim_release(dbname_s); + heim_release(bkpname_s); + return ENOMEM; + } + + jsondb->last_read_time = time(NULL); + jsondb->fd = -1; + jsondb->dbname = dbname_s; + jsondb->bkpname = bkpname_s; + jsondb->read_only = 0; + + if (contents != NULL) + jsondb->dict = contents; + else { + jsondb->dict = heim_dict_create(29); + if (jsondb->dict == NULL) { + heim_release(jsondb); + return ENOMEM; + } + } + + *db = jsondb; + return 0; +} + +static int +json_db_close(void *db, heim_error_t *error) +{ + json_db_t jsondb = db; + + if (error) + *error = NULL; + if (jsondb->fd > -1) + (void) close(jsondb->fd); + jsondb->fd = -1; + heim_release(jsondb->dbname); + heim_release(jsondb->bkpname); + heim_release(jsondb->dict); + heim_release(jsondb); + return 0; +} + +static int +json_db_lock(void *db, int read_only, heim_error_t *error) +{ + json_db_t jsondb = db; + int ret; + + heim_assert(jsondb->fd == -1 || (jsondb->read_only && !read_only), + "DB locks are not recursive"); + + jsondb->read_only = read_only ? 1 : 0; + if (jsondb->fd > -1) + return 0; + + ret = open_file(heim_string_get_utf8(jsondb->bkpname), 1, 1, &jsondb->fd, error); + if (ret == 0) { + jsondb->locked_needs_unlink = 1; + jsondb->locked = 1; + } + return ret; +} + +static int +json_db_unlock(void *db, heim_error_t *error) +{ + json_db_t jsondb = db; + int ret = 0; + + heim_assert(jsondb->locked, "DB not locked when unlock attempted"); + if (jsondb->fd > -1) + ret = close(jsondb->fd); + jsondb->fd = -1; + jsondb->read_only = 0; + jsondb->locked = 0; + if (jsondb->locked_needs_unlink) + unlink(heim_string_get_utf8(jsondb->bkpname)); + jsondb->locked_needs_unlink = 0; + return ret; +} + +static int +json_db_sync(void *db, heim_error_t *error) +{ + json_db_t jsondb = db; + size_t len, bytes; + heim_error_t e; + heim_string_t json; + const char *json_text = NULL; + int ret = 0; + int fd = -1; +#ifdef WIN32 + int tries = 3; +#endif + + heim_assert(jsondb->fd > -1, "DB not locked when sync attempted"); + + json = heim_json_copy_serialize(jsondb->dict, 0, &e); + if (json == NULL) { + ret = heim_error_get_code(e); + if (error) + *error = e; + else + heim_release(e); + return ret; + } + + json_text = heim_string_get_utf8(json); + len = strlen(json_text); + errno = 0; + +#ifdef WIN32 + while (tries--) { + ret = open_file(heim_string_get_utf8(jsondb->dbname), 1, 0, &fd, error); + if (ret == 0) + break; + sleep(1); + } + if (ret) { + heim_release(json); + return ret; + } +#else + fd = jsondb->fd; +#endif /* WIN32 */ + + bytes = write(fd, json_text, len); + heim_release(json); + if (bytes != len) + return errno ? errno : EIO; + ret = fsync(fd); + if (ret) + return ret; + +#ifdef WIN32 + ret = close(fd); + if (ret) + return GetLastError(); +#else + ret = rename(heim_string_get_utf8(jsondb->bkpname), heim_string_get_utf8(jsondb->dbname)); + if (ret == 0) { + jsondb->locked_needs_unlink = 0; + return 0; + } +#endif /* WIN32 */ + + return errno; +} + +static heim_data_t +json_db_copy_value(void *db, heim_string_t table, heim_data_t key, + heim_error_t *error) +{ + json_db_t jsondb = db; + heim_string_t key_string; + const heim_octet_string *key_data = heim_data_get_data(key); + struct stat st; + heim_data_t result; + + if (error) + *error = NULL; + + if (strnlen(key_data->data, key_data->length) != key_data->length) { + HEIM_ERROR(error, EINVAL, + (EINVAL, N_("JSON DB requires keys that are actually " + "strings", ""))); + return NULL; + } + + if (stat(heim_string_get_utf8(jsondb->dbname), &st) == -1) { + HEIM_ERROR(error, errno, + (errno, N_("Could not stat JSON DB file", ""))); + return NULL; + } + + if (st.st_mtime > jsondb->last_read_time || + st.st_ctime > jsondb->last_read_time) { + heim_dict_t contents = NULL; + int ret; + + /* Ignore file is gone (ENOENT) */ + ret = read_json(heim_string_get_utf8(jsondb->dbname), + (heim_object_t *)&contents, error); + if (ret) + return NULL; + if (contents == NULL) + contents = heim_dict_create(29); + heim_release(jsondb->dict); + jsondb->dict = contents; + jsondb->last_read_time = time(NULL); + } + + key_string = heim_string_create_with_bytes(key_data->data, + key_data->length); + if (key_string == NULL) { + (void) HEIM_ENOMEM(error); + return NULL; + } + + result = heim_path_copy(jsondb->dict, error, table, key_string, NULL); + heim_release(key_string); + return result; +} + +static int +json_db_set_value(void *db, heim_string_t table, + heim_data_t key, heim_data_t value, heim_error_t *error) +{ + json_db_t jsondb = db; + heim_string_t key_string; + const heim_octet_string *key_data = heim_data_get_data(key); + int ret; + + if (error) + *error = NULL; + + if (strnlen(key_data->data, key_data->length) != key_data->length) + return HEIM_ERROR(error, EINVAL, + (EINVAL, + N_("JSON DB requires keys that are actually strings", + ""))); + + key_string = heim_string_create_with_bytes(key_data->data, + key_data->length); + if (key_string == NULL) + return HEIM_ENOMEM(error); + + if (table == NULL) + table = HSTR(""); + + ret = heim_path_create(jsondb->dict, 29, value, error, table, key_string, NULL); + heim_release(key_string); + return ret; +} + +static int +json_db_del_key(void *db, heim_string_t table, heim_data_t key, + heim_error_t *error) +{ + json_db_t jsondb = db; + heim_string_t key_string; + const heim_octet_string *key_data = heim_data_get_data(key); + + if (error) + *error = NULL; + + if (strnlen(key_data->data, key_data->length) != key_data->length) + return HEIM_ERROR(error, EINVAL, + (EINVAL, + N_("JSON DB requires keys that are actually strings", + ""))); + + key_string = heim_string_create_with_bytes(key_data->data, + key_data->length); + if (key_string == NULL) + return HEIM_ENOMEM(error); + + if (table == NULL) + table = HSTR(""); + + heim_path_delete(jsondb->dict, error, table, key_string, NULL); + heim_release(key_string); + return 0; +} + +struct json_db_iter_ctx { + heim_db_iterator_f_t iter_f; + void *iter_ctx; +}; + +static void json_db_iter_f(heim_object_t key, heim_object_t value, void *arg) +{ + struct json_db_iter_ctx *ctx = arg; + const char *key_string; + heim_data_t key_data; + + key_string = heim_string_get_utf8((heim_string_t)key); + key_data = heim_data_ref_create(key_string, strlen(key_string), NULL); + ctx->iter_f(key_data, (heim_object_t)value, ctx->iter_ctx); + heim_release(key_data); +} + +static void +json_db_iter(void *db, heim_string_t table, void *iter_data, + heim_db_iterator_f_t iter_f, heim_error_t *error) +{ + json_db_t jsondb = db; + struct json_db_iter_ctx ctx; + heim_dict_t table_dict; + + if (error) + *error = NULL; + + if (table == NULL) + table = HSTR(""); + + table_dict = heim_dict_get_value(jsondb->dict, table); + if (table_dict == NULL) + return; + + ctx.iter_ctx = iter_data; + ctx.iter_f = iter_f; + + heim_dict_iterate_f(table_dict, &ctx, json_db_iter_f); +} + +static struct heim_db_type json_dbt = { + 1, json_db_open, NULL, json_db_close, + json_db_lock, json_db_unlock, json_db_sync, + NULL, NULL, NULL, + json_db_copy_value, json_db_set_value, + json_db_del_key, json_db_iter +}; + diff --git a/third_party/heimdal/lib/base/dict.c b/third_party/heimdal/lib/base/dict.c new file mode 100644 index 0000000..86be109 --- /dev/null +++ b/third_party/heimdal/lib/base/dict.c @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2002, 1997 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" + +struct hashentry { + struct hashentry **prev; + struct hashentry *next; + heim_object_t key; + heim_object_t value; +}; + +struct heim_dict_data { + size_t size; + struct hashentry **tab; +}; + +static void HEIM_CALLCONV +dict_dealloc(void *ptr) +{ + heim_dict_t dict = ptr; + struct hashentry **h, *g, *i; + + for (h = dict->tab; h < &dict->tab[dict->size]; ++h) { + for (g = h[0]; g; g = i) { + i = g->next; + heim_release(g->key); + heim_release(g->value); + free(g); + } + } + free(dict->tab); +} + +struct heim_type_data dict_object = { + HEIM_TID_DICT, + "dict-object", + NULL, + dict_dealloc, + NULL, + NULL, + NULL, + NULL +}; + +static size_t +isprime(size_t p) +{ + size_t q, i; + + for(i = 2 ; i < p; i++) { + q = p / i; + + if (i * q == p) + return 0; + if (i * i > p) + return 1; + } + return 1; +} + +static size_t +findprime(size_t p) +{ + if (p % 2 == 0) + p++; + + while (isprime(p) == 0) + p += 2; + + return p; +} + +/** + * Allocate an array + * + * @return A new allocated array, free with heim_release() + */ + +heim_dict_t +heim_dict_create(size_t size) +{ + heim_dict_t dict; + + dict = _heim_alloc_object(&dict_object, sizeof(*dict)); + if (dict == NULL) + return NULL; + + dict->size = findprime(size); + if (dict->size == 0) { + heim_release(dict); + return NULL; + } + + dict->tab = calloc(dict->size, sizeof(dict->tab[0])); + if (dict->tab == NULL) { + dict->size = 0; + heim_release(dict); + return NULL; + } + + return dict; +} + +/** + * Get type id of an dict + * + * @return the type id + */ + +heim_tid_t +heim_dict_get_type_id(void) +{ + return HEIM_TID_DICT; +} + +/* Intern search function */ + +static struct hashentry * +_search(heim_dict_t dict, heim_object_t ptr) +{ + uintptr_t v = heim_get_hash(ptr); + struct hashentry *p; + + for (p = dict->tab[v % dict->size]; p != NULL; p = p->next) + if (heim_cmp(ptr, p->key) == 0) + return p; + + return NULL; +} + +/** + * Search for element in hash table + * + * @value dict the dict to search in + * @value key the key to search for + * + * @return a not-retained copy of the value for key or NULL if not found + */ + +heim_object_t +heim_dict_get_value(heim_dict_t dict, heim_object_t key) +{ + struct hashentry *p; + p = _search(dict, key); + if (p == NULL) + return NULL; + + return p->value; +} + +/** + * Search for element in hash table + * + * @value dict the dict to search in + * @value key the key to search for + * + * @return a retained copy of the value for key or NULL if not found + */ + +heim_object_t +heim_dict_copy_value(heim_dict_t dict, heim_object_t key) +{ + struct hashentry *p; + p = _search(dict, key); + if (p == NULL) + return NULL; + + return heim_retain(p->value); +} + +/** + * Add key and value to dict + * + * @value dict the dict to add too + * @value key the key to add + * @value value the value to add + * + * @return 0 if added, errno if not + */ + +int +heim_dict_set_value(heim_dict_t dict, heim_object_t key, heim_object_t value) +{ + struct hashentry **tabptr, *h; + + h = _search(dict, key); + if (h) { + heim_release(h->value); + h->value = heim_retain(value); + } else { + uintptr_t v; + + h = malloc(sizeof(*h)); + if (h == NULL) + return ENOMEM; + + h->key = heim_retain(key); + h->value = heim_retain(value); + + v = heim_get_hash(key); + + tabptr = &dict->tab[v % dict->size]; + h->next = *tabptr; + *tabptr = h; + h->prev = tabptr; + if (h->next) + h->next->prev = &h->next; + } + + return 0; +} + +/** + * Delete element with key key + * + * @value dict the dict to delete from + * @value key the key to delete + */ + +void +heim_dict_delete_key(heim_dict_t dict, heim_object_t key) +{ + struct hashentry *h = _search(dict, key); + + if (h == NULL) + return; + + heim_release(h->key); + heim_release(h->value); + + if ((*(h->prev) = h->next) != NULL) + h->next->prev = h->prev; + + free(h); +} + +/** + * Do something for each element + * + * @value dict the dict to interate over + * @value func the function to search for + * @value arg argument to func + */ + +void +heim_dict_iterate_f(heim_dict_t dict, void *arg, heim_dict_iterator_f_t func) +{ + struct hashentry **h, *g; + + for (h = dict->tab; h < &dict->tab[dict->size]; ++h) + for (g = *h; g; g = g->next) + func(g->key, g->value, arg); +} + +#ifdef __BLOCKS__ +/** + * Do something for each element + * + * @value dict the dict to interate over + * @value func the function to search for + */ + +void +heim_dict_iterate(heim_dict_t dict, void (^func)(heim_object_t, heim_object_t)) +{ + struct hashentry **h, *g; + + for (h = dict->tab; h < &dict->tab[dict->size]; ++h) + for (g = *h; g; g = g->next) + func(g->key, g->value); +} +#endif diff --git a/third_party/heimdal/lib/base/dll.c b/third_party/heimdal/lib/base/dll.c new file mode 100644 index 0000000..53f1f63 --- /dev/null +++ b/third_party/heimdal/lib/base/dll.c @@ -0,0 +1,325 @@ +/*********************************************************************** + * Copyright (c) 2016 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + **********************************************************************/ + +/* + * This is an implementation of thread-specific storage with + * destructors. WIN32 doesn't quite have this. Instead it has + * DllMain(), an entry point in every DLL that gets called to notify the + * DLL of thread/process "attach"/"detach" events. + * + * We use __thread (or __declspec(thread)) for the thread-local itself + * and DllMain() DLL_THREAD_DETACH events to drive destruction of + * thread-local values. + * + * When building in maintainer mode on non-Windows pthread systems this + * uses a single pthread key instead to implement multiple keys. This + * keeps the code from rotting when modified by non-Windows developers. + */ + +#include "baselocl.h" + +#ifdef WIN32 +#include +#endif + +#ifdef HEIM_WIN32_TLS +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +/* Logical array of keys that grows lock-lessly */ +typedef struct tls_keys tls_keys; +struct tls_keys { + void (**keys_dtors)(void *); /* array of destructors */ + size_t keys_start_idx; /* index of first destructor */ + size_t keys_num; + tls_keys *keys_next; +}; + +/* + * Well, not quite locklessly. We need synchronization primitives to do + * this locklessly. An atomic CAS will do. + */ +static HEIMDAL_MUTEX tls_key_defs_lock = HEIMDAL_MUTEX_INITIALIZER; +static tls_keys *tls_key_defs; + +/* Logical array of values (per-thread; no locking needed here) */ +struct tls_values { + void **values; /* realloc()ed */ + size_t values_num; +}; + +static HEIMDAL_THREAD_LOCAL struct tls_values values; + +static char dead_key[1]; +static void no_dtor(void *d) { (void)d; } + +void +heim_w32_service_thread_detach(void *unused) +{ + tls_keys *key_defs; + void (*dtor)(void*); + size_t i; + + HEIMDAL_MUTEX_lock(&tls_key_defs_lock); + key_defs = tls_key_defs; + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + + if (key_defs == NULL) + return; + + for (i = 0; i < values.values_num; i++) { + assert(i >= key_defs->keys_start_idx); + if (i >= key_defs->keys_start_idx + key_defs->keys_num) { + HEIMDAL_MUTEX_lock(&tls_key_defs_lock); + key_defs = key_defs->keys_next; + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + + assert(key_defs != NULL); + assert(i >= key_defs->keys_start_idx); + assert(i < key_defs->keys_start_idx + key_defs->keys_num); + } + dtor = key_defs->keys_dtors[i - key_defs->keys_start_idx]; + if (values.values[i] != NULL && dtor != NULL && dtor != no_dtor) + dtor(values.values[i]); + values.values[i] = NULL; + } +} + +#if !defined(WIN32) +static pthread_key_t pt_key; +pthread_once_t pt_once = PTHREAD_ONCE_INIT; + +static void +atexit_del_tls_for_thread(void) +{ + heim_w32_service_thread_detach(NULL); +} + +static void +create_pt_key(void) +{ + int ret; + + /* The main thread may not execute TLS destructors */ + atexit(atexit_del_tls_for_thread); + ret = pthread_key_create(&pt_key, heim_w32_service_thread_detach); + if (ret != 0) + err(1, "pthread_key_create() failed"); +} + +#endif + +int +heim_w32_key_create(HEIM_PRIV_thread_key *key, void (*dtor)(void *)) +{ + tls_keys *key_defs, *new_key_defs; + size_t i, k; + int ret = ENOMEM; + +#if !defined(WIN32) + (void) pthread_once(&pt_once, create_pt_key); + (void) pthread_setspecific(pt_key, dead_key); +#endif + + HEIMDAL_MUTEX_lock(&tls_key_defs_lock); + if (tls_key_defs == NULL) { + /* First key */ + new_key_defs = calloc(1, sizeof(*new_key_defs)); + if (new_key_defs == NULL) { + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + return ENOMEM; + } + new_key_defs->keys_num = 8; + new_key_defs->keys_dtors = calloc(new_key_defs->keys_num, + sizeof(*new_key_defs->keys_dtors)); + if (new_key_defs->keys_dtors == NULL) { + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + free(new_key_defs); + return ENOMEM; + } + tls_key_defs = new_key_defs; + new_key_defs->keys_dtors[0] = dtor; + for (i = 1; i < new_key_defs->keys_num; i++) + new_key_defs->keys_dtors[i] = NULL; + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + return 0; + } + + for (key_defs = tls_key_defs; + key_defs != NULL; + key_defs = key_defs->keys_next) { + k = key_defs->keys_start_idx; + for (i = 0; i < key_defs->keys_num; i++, k++) { + if (key_defs->keys_dtors[i] == NULL) { + /* Found free slot; use it */ + key_defs->keys_dtors[i] = dtor; + *key = k; + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + return 0; + } + } + if (key_defs->keys_next != NULL) + continue; + + /* Grow the registration array */ + /* XXX DRY */ + new_key_defs = calloc(1, sizeof(*new_key_defs)); + if (new_key_defs == NULL) + break; + + new_key_defs->keys_dtors = + calloc(key_defs->keys_num + key_defs->keys_num / 2, + sizeof(*new_key_defs->keys_dtors)); + if (new_key_defs->keys_dtors == NULL) { + free(new_key_defs); + break; + } + new_key_defs->keys_start_idx = key_defs->keys_start_idx + + key_defs->keys_num; + new_key_defs->keys_num = key_defs->keys_num + key_defs->keys_num / 2; + new_key_defs->keys_dtors[i] = dtor; + for (i = 1; i < new_key_defs->keys_num; i++) + new_key_defs->keys_dtors[i] = NULL; + key_defs->keys_next = new_key_defs; + ret = 0; + break; + } + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + return ret; +} + +static void +key_lookup(HEIM_PRIV_thread_key key, tls_keys **kd, + size_t *dtor_idx, void (**dtor)(void *)) +{ + tls_keys *key_defs; + + if (kd != NULL) + *kd = NULL; + if (dtor_idx != NULL) + *dtor_idx = 0; + if (dtor != NULL) + *dtor = NULL; + + HEIMDAL_MUTEX_lock(&tls_key_defs_lock); + key_defs = tls_key_defs; + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + + while (key_defs != NULL) { + if (key >= key_defs->keys_start_idx && + key < key_defs->keys_start_idx + key_defs->keys_num) { + if (kd != NULL) + *kd = key_defs; + if (dtor_idx != NULL) + *dtor_idx = key - key_defs->keys_start_idx; + if (dtor != NULL) + *dtor = key_defs->keys_dtors[key - key_defs->keys_start_idx]; + return; + } + + HEIMDAL_MUTEX_lock(&tls_key_defs_lock); + key_defs = key_defs->keys_next; + HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); + assert(key_defs != NULL); + assert(key >= key_defs->keys_start_idx); + } +} + +int +heim_w32_delete_key(HEIM_PRIV_thread_key key) +{ + tls_keys *key_defs; + size_t dtor_idx; + + key_lookup(key, &key_defs, &dtor_idx, NULL); + if (key_defs == NULL) + return EINVAL; + key_defs->keys_dtors[dtor_idx] = no_dtor; + return 0; +} + +int +heim_w32_setspecific(HEIM_PRIV_thread_key key, void *value) +{ + void **new_values; + size_t new_num; + void (*dtor)(void *); + size_t i; + +#if !defined(WIN32) + (void) pthread_setspecific(pt_key, dead_key); +#endif + + key_lookup(key, NULL, NULL, &dtor); + if (dtor == NULL) + return EINVAL; + + if (key >= values.values_num) { + if (values.values_num == 0) { + values.values = NULL; + new_num = 8; + } else { + new_num = (values.values_num + values.values_num / 2); + } + new_values = realloc(values.values, sizeof(void *) * new_num); + if (new_values == NULL) + return ENOMEM; + for (i = values.values_num; i < new_num; i++) + new_values[i] = NULL; + values.values = new_values; + values.values_num = new_num; + } + + assert(key < values.values_num); + + if (values.values[key] != NULL && dtor != NULL && dtor != no_dtor) + dtor(values.values[key]); + + values.values[key] = value; + return 0; +} + +void * +heim_w32_getspecific(HEIM_PRIV_thread_key key) +{ + if (key >= values.values_num) + return NULL; + return values.values[key]; +} + +#else +static char dummy; +#endif /* HEIM_WIN32_TLS */ diff --git a/third_party/heimdal/lib/base/error.c b/third_party/heimdal/lib/base/error.c new file mode 100644 index 0000000..bc289d3 --- /dev/null +++ b/third_party/heimdal/lib/base/error.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" + +struct heim_error { + int error_code; + heim_string_t msg; + struct heim_error *next; +}; + +static void HEIM_CALLCONV +error_dealloc(void *ptr) +{ + struct heim_error *p = ptr; + heim_release(p->msg); + heim_release(p->next); +} + +static int +error_cmp(void *a, void *b) +{ + struct heim_error *ap = a, *bp = b; + if (ap->error_code == bp->error_code) + return 0; + return heim_cmp(ap->msg, bp->msg); +} + +static uintptr_t +error_hash(void *ptr) +{ + struct heim_error *p = ptr; + return p->error_code; +} + +struct heim_type_data _heim_error_object = { + HEIM_TID_ERROR, + "error-object", + NULL, + error_dealloc, + NULL, + error_cmp, + error_hash, + NULL +}; + +heim_error_t +heim_error_create_enomem(void) +{ + /* This is an immediate object; see heim_number_create() */ + return (heim_error_t)heim_number_create(ENOMEM); +} + + +void +heim_error_create_opt(heim_error_t *error, int error_code, const char *fmt, ...) + HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 3, 4)) +{ + if (error) { + va_list ap; + va_start(ap, fmt); + *error = heim_error_createv(error_code, fmt, ap); + va_end(ap); + } +} + +heim_error_t +heim_error_create(int error_code, const char *fmt, ...) + HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 2, 3)) +{ + heim_error_t e; + va_list ap; + + va_start(ap, fmt); + e = heim_error_createv(error_code, fmt, ap); + va_end(ap); + + return e; +} + +heim_error_t +heim_error_createv(int error_code, const char *fmt, va_list ap) + HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 2, 0)) +{ + heim_error_t e; + char *str; + int len; + int save_errno = errno; + + str = malloc(1024); + errno = save_errno; + if (str == NULL) + return heim_error_create_enomem(); + len = vsnprintf(str, 1024, fmt, ap); + errno = save_errno; + if (len < 0) { + free(str); + return NULL; /* XXX We should have a special heim_error_t for this */ + } + + e = _heim_alloc_object(&_heim_error_object, sizeof(struct heim_error)); + if (e) { + e->msg = heim_string_create(str); + e->error_code = error_code; + } + free(str); + + errno = save_errno; + return e; +} + +heim_string_t +heim_error_copy_string(heim_error_t error) +{ + if (heim_get_tid(error) != HEIM_TID_ERROR) { + if (heim_get_tid(error) == heim_number_get_type_id()) + return __heim_string_constant(strerror(heim_number_get_int((heim_number_t)error))); + heim_abort("invalid heim_error_t"); + } + /* XXX concat all strings */ + return heim_retain(error->msg); +} + +int +heim_error_get_code(heim_error_t error) +{ + if (error == NULL) + return -1; + if (heim_get_tid(error) != HEIM_TID_ERROR) { + if (heim_get_tid(error) == heim_number_get_type_id()) + return heim_number_get_int((heim_number_t)error); + heim_abort("invalid heim_error_t"); + } + return error->error_code; +} + +heim_error_t +heim_error_append(heim_error_t top, heim_error_t append) +{ + if (heim_get_tid(top) != HEIM_TID_ERROR) { + if (heim_get_tid(top) == heim_number_get_type_id()) + return top; + heim_abort("invalid heim_error_t"); + } + if (top->next) + heim_release(top->next); + top->next = heim_retain(append); + return top; +} diff --git a/third_party/heimdal/lib/base/error_string.c b/third_party/heimdal/lib/base/error_string.c new file mode 100644 index 0000000..a562833 --- /dev/null +++ b/third_party/heimdal/lib/base/error_string.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2001, 2003, 2005 - 2020 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" + +#undef __attribute__ +#define __attribute__(x) + +void +heim_clear_error_message(heim_context context) +{ + if (!context) + return; + if (context->error_string) + free(context->error_string); + context->error_code = 0; + context->error_string = NULL; +} + +void +heim_set_error_message(heim_context context, heim_error_code ret, + const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))) +{ + va_list ap; + + va_start(ap, fmt); + if (context) + heim_vset_error_message(context, ret, fmt, ap); + va_end(ap); +} + +void +heim_vset_error_message(heim_context context, heim_error_code ret, + const char *fmt, va_list args) + __attribute__ ((__format__ (__printf__, 3, 0))) +{ + int r; + + if (context == NULL) + return; + if (context->error_string) { + free(context->error_string); + context->error_string = NULL; + } + context->error_code = ret; + r = vasprintf(&context->error_string, fmt, args); + if (r < 0) + context->error_string = NULL; + if (context->error_string) + heim_debug(context, 200, "error message: %s: %d", context->error_string, ret); +} + +void +heim_prepend_error_message(heim_context context, heim_error_code ret, + const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))) +{ + va_list ap; + + va_start(ap, fmt); + heim_vprepend_error_message(context, ret, fmt, ap); + va_end(ap); +} + +void +heim_vprepend_error_message(heim_context context, heim_error_code ret, + const char *fmt, va_list args) + __attribute__ ((__format__ (__printf__, 3, 0))) +{ + char *str = NULL, *str2 = NULL; + + if (context == NULL || context->error_code != ret || + vasprintf(&str, fmt, args) < 0 || str == NULL) + return; + if (context->error_string) { + int e; + + e = asprintf(&str2, "%s: %s", str, context->error_string); + free(context->error_string); + if (e < 0 || str2 == NULL) + context->error_string = NULL; + else + context->error_string = str2; + free(str); + } else + context->error_string = str; +} + +const char * +heim_get_error_message(heim_context context, heim_error_code code) +{ + const char *cstr = NULL; + char *str = NULL; + char buf[128]; + int free_context = 0; + + if (code == 0) + return strdup("Success"); + + /* + * The MIT version of this function ignores the krb5_context + * and several widely deployed applications call krb5_get_error_message() + * with a NULL context in order to translate an error code as a + * replacement for error_message(). Another reason a NULL context + * might be provided is if the krb5_init_context() call itself + * failed. + */ + if (context && + context->error_string && + (code == context->error_code || context->error_code == 0) && + (cstr = strdup(context->error_string))) + return cstr; + + if (context == NULL && (context = heim_context_init())) + free_context = 1; + if (context) + cstr = com_right_r(context->et_list, code, buf, sizeof(buf)); + if (free_context) + heim_context_free(&context); + + if (cstr || (cstr = error_message(code))) + return strdup(cstr); + if (asprintf(&str, "", (int)code) == -1 || str == NULL) + return NULL; + return str; +} + +const char * +heim_get_error_string(heim_context context) +{ + if (context && context->error_string) + return strdup(context->error_string); + return NULL; +} + +int +heim_have_error_string(heim_context context) +{ + return context && context->error_string != NULL; +} + +void +heim_free_error_message(heim_context context, const char *msg) +{ + free(rk_UNCONST(msg)); +} diff --git a/third_party/heimdal/lib/base/expand_path.c b/third_party/heimdal/lib/base/expand_path.c new file mode 100644 index 0000000..cf24991 --- /dev/null +++ b/third_party/heimdal/lib/base/expand_path.c @@ -0,0 +1,725 @@ + +/*********************************************************************** + * Copyright (c) 2009-2020, Secure Endpoints Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + **********************************************************************/ + +#include "baselocl.h" + +#include + +typedef int PTYPE; + +#ifdef _WIN32 +#include +#include + +/* + * Expand a %{TEMP} token + * + * The %{TEMP} token expands to the temporary path for the current + * user as returned by GetTempPath(). + * + * @note: Since the GetTempPath() function relies on the TMP or TEMP + * environment variables, this function will failover to the system + * temporary directory until the user profile is loaded. In addition, + * the returned path may or may not exist. + */ +static heim_error_code +expand_temp_folder(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **ret) +{ + TCHAR tpath[MAX_PATH]; + size_t len; + + if (!GetTempPath(sizeof(tpath)/sizeof(tpath[0]), tpath)) { + heim_set_error_message(context, EINVAL, + "Failed to get temporary path (GLE=%d)", + GetLastError()); + return EINVAL; + } + + len = strlen(tpath); + + if (len > 0 && tpath[len - 1] == '\\') + tpath[len - 1] = '\0'; + + *ret = strdup(tpath); + + if (*ret == NULL) + return heim_enomem(context); + + return 0; +} + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +/* + * Expand a %{BINDIR} token + * + * This is also used to expand a few other tokens on Windows, since + * most of the executable binaries end up in the same directory. The + * "bin" directory is considered to be the directory in which the + * containing DLL is located. + */ +static heim_error_code +expand_bin_dir(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **ret) +{ + TCHAR path[MAX_PATH]; + TCHAR *lastSlash; + DWORD nc; + + nc = GetModuleFileName((HINSTANCE)&__ImageBase, path, + sizeof(path)/sizeof(path[0])); + if (nc == 0 || + nc == sizeof(path)/sizeof(path[0])) { + return EINVAL; + } + + lastSlash = strrchr(path, '\\'); + if (lastSlash != NULL) { + TCHAR *fslash = strrchr(lastSlash, '/'); + + if (fslash != NULL) + lastSlash = fslash; + + *lastSlash = '\0'; + } + + if (postfix) { + if (strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0])) + return EINVAL; + } + + *ret = strdup(path); + if (*ret == NULL) + return heim_enomem(context); + + return 0; +} + +/* + * Expand a %{USERID} token + * + * The %{USERID} token expands to the string representation of the + * user's SID. The user account that will be used is the account + * corresponding to the current thread's security token. This means + * that: + * + * - If the current thread token has the anonymous impersonation + * level, the call will fail. + * + * - If the current thread is impersonating a token at + * SecurityIdentification level the call will fail. + * + */ +static heim_error_code +expand_userid(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **ret) +{ + int rv = EINVAL; + HANDLE hThread = NULL; + HANDLE hToken = NULL; + PTOKEN_OWNER pOwner = NULL; + DWORD len = 0; + LPTSTR strSid = NULL; + + hThread = GetCurrentThread(); + + if (!OpenThreadToken(hThread, TOKEN_QUERY, + FALSE, /* Open the thread token as the + current thread user. */ + &hToken)) { + + DWORD le = GetLastError(); + + if (le == ERROR_NO_TOKEN) { + HANDLE hProcess = GetCurrentProcess(); + + le = 0; + if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) + le = GetLastError(); + } + + if (le != 0) { + heim_set_error_message(context, rv, + "Can't open thread token (GLE=%d)", le); + goto _exit; + } + } + + if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + heim_set_error_message(context, rv, + "Unexpected error reading token information (GLE=%d)", + GetLastError()); + goto _exit; + } + + if (len == 0) { + heim_set_error_message(context, rv, + "GetTokenInformation() returned truncated buffer"); + goto _exit; + } + + pOwner = malloc(len); + if (pOwner == NULL) { + heim_set_error_message(context, rv, "Out of memory"); + goto _exit; + } + } else { + heim_set_error_message(context, rv, "GetTokenInformation() returned truncated buffer"); + goto _exit; + } + + if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) { + heim_set_error_message(context, rv, + "GetTokenInformation() failed. GLE=%d", + GetLastError()); + goto _exit; + } + + if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) { + heim_set_error_message(context, rv, + "Can't convert SID to string. GLE=%d", + GetLastError()); + goto _exit; + } + + *ret = strdup(strSid); + if (*ret == NULL) + heim_set_error_message(context, rv, "Out of memory"); + + rv = 0; + + _exit: + if (hToken != NULL) + CloseHandle(hToken); + + if (pOwner != NULL) + free (pOwner); + + if (strSid != NULL) + LocalFree(strSid); + + return rv; +} + +/* + * Expand a folder identified by a CSIDL + */ + +static heim_error_code +expand_csidl(heim_context context, PTYPE folder, const char *postfix, + const char *arg, char **ret) +{ + TCHAR path[MAX_PATH]; + size_t len; + + if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path) != S_OK) { + heim_set_error_message(context, EINVAL, "Unable to determine folder path"); + return EINVAL; + } + + len = strlen(path); + + if (len > 0 && path[len - 1] == '\\') + path[len - 1] = '\0'; + + if (postfix && + strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0])) + return heim_enomem(context); + + *ret = strdup(path); + if (*ret == NULL) + return heim_enomem(context); + return 0; +} + +#else + +static heim_error_code +expand_path(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **ret) +{ + *ret = strdup(postfix); + if (*ret == NULL) + return heim_enomem(context); + return 0; +} + +static heim_error_code +expand_temp_folder(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **ret) +{ + const char *p = NULL; + + p = secure_getenv("TEMP"); + + if (p) + *ret = strdup(p); + else + *ret = strdup("/tmp"); + if (*ret == NULL) + return heim_enomem(context); + return 0; +} + +static heim_error_code +expand_userid(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **str) +{ + int ret = asprintf(str, "%ld", (unsigned long)getuid()); + if (ret < 0 || *str == NULL) + return heim_enomem(context); + return 0; +} + +static heim_error_code +expand_euid(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **str) +{ + int ret = asprintf(str, "%ld", (unsigned long)geteuid()); + if (ret < 0 || *str == NULL) + return heim_enomem(context); + return 0; +} +#endif /* _WIN32 */ + +static heim_error_code +expand_home(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **str) +{ + char homedir[MAX_PATH]; + int ret; + + if (roken_get_homedir(homedir, sizeof(homedir))) + ret = asprintf(str, "%s", homedir); + else + ret = asprintf(str, "/unknown"); + if (ret < 0 || *str == NULL) + return heim_enomem(context); + return 0; +} + +static heim_error_code +expand_username(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **str) +{ + char user[128]; + const char *username = roken_get_username(user, sizeof(user)); + + if (username == NULL) { + heim_set_error_message(context, ENOTTY, + N_("unable to figure out current principal", + "")); + return ENOTTY; /* XXX */ + } + + *str = strdup(username); + if (*str == NULL) + return heim_enomem(context); + + return 0; +} + +static heim_error_code +expand_loginname(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **str) +{ + char user[128]; + const char *username = roken_get_loginname(user, sizeof(user)); + + if (username == NULL) { + heim_set_error_message(context, ENOTTY, + N_("unable to figure out current principal", + "")); + return ENOTTY; /* XXX */ + } + + *str = strdup(username); + if (*str == NULL) + return heim_enomem(context); + + return 0; +} + +static heim_error_code +expand_strftime(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **ret) +{ + size_t len; + time_t t; + char buf[1024]; + + t = time(NULL); + len = strftime(buf, sizeof(buf), arg, localtime(&t)); + if (len == 0 || len >= sizeof(buf)) + return heim_enomem(context); + *ret = strdup(buf); + return 0; +} + +/** + * Expand an extra token + */ + +static heim_error_code +expand_extra_token(heim_context context, const char *value, char **ret) +{ + *ret = strdup(value); + if (*ret == NULL) + return heim_enomem(context); + return 0; +} + +/** + * Expand a %{null} token + * + * The expansion of a %{null} token is always the empty string. + */ + +static heim_error_code +expand_null(heim_context context, PTYPE param, const char *postfix, + const char *arg, char **ret) +{ + *ret = strdup(""); + if (*ret == NULL) + return heim_enomem(context); + return 0; +} + + +static const struct { + const char * tok; + int ftype; +#define FTYPE_CSIDL 0 +#define FTYPE_SPECIAL 1 + + PTYPE param; + const char * postfix; + + int (*exp_func)(heim_context, PTYPE, const char *, const char *, char **); + +#define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f +#define SPECIAL(f) SPECIALP(f, NULL) + +} tokens[] = { +#ifdef _WIN32 +#define CSIDLP(C,P) FTYPE_CSIDL, C, P, expand_csidl +#define CSIDL(C) CSIDLP(C, NULL) + + {"APPDATA", CSIDL(CSIDL_APPDATA)}, /* Roaming application data (for current user) */ + {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)}, /* Application data (all users) */ + {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)}, /* Local application data (for current user) */ + {"SYSTEM", CSIDL(CSIDL_SYSTEM)}, /* Windows System folder (e.g. %WINDIR%\System32) */ + {"WINDOWS", CSIDL(CSIDL_WINDOWS)}, /* Windows folder */ + {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)}, /* Per user Heimdal configuration file path */ + {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)}, /* Common Heimdal configuration file path */ + {"LIBDIR", SPECIAL(expand_bin_dir)}, + {"BINDIR", SPECIAL(expand_bin_dir)}, + {"LIBEXEC", SPECIAL(expand_bin_dir)}, + {"SBINDIR", SPECIAL(expand_bin_dir)}, +#else + {"LOCALSTATEDIR", FTYPE_SPECIAL, 0, LOCALSTATEDIR, expand_path}, + {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, expand_path}, + {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, expand_path}, + {"LIBEXEC", FTYPE_SPECIAL, 0, LIBEXECDIR, expand_path}, + {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, expand_path}, + {"USERCONFIG", SPECIAL(expand_home)}, /* same as %{HOME} on not-Windows */ + {"euid", SPECIAL(expand_euid)}, + {"ruid", SPECIAL(expand_userid)}, + {"loginname", SPECIAL(expand_loginname)}, +#endif + {"username", SPECIAL(expand_username)}, + {"TEMP", SPECIAL(expand_temp_folder)}, + {"USERID", SPECIAL(expand_userid)}, + {"uid", SPECIAL(expand_userid)}, + {"null", SPECIAL(expand_null)}, + {"strftime", SPECIAL(expand_strftime)}, + {"HOME", SPECIAL(expand_home)}, +}; + +static heim_error_code +expand_token(heim_context context, + const char *token, + const char *token_end, + char **extra_tokens, + char **ret) +{ + heim_error_code errcode; + size_t i; + char **p; + const char *colon; + + *ret = NULL; + + if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' || + token_end - token <= 2) { + heim_set_error_message(context, EINVAL,"Invalid token."); + return EINVAL; + } + + for (p = extra_tokens; p && p[0]; p += 2) { + if (strncmp(token+2, p[0], (token_end - token) - 2) == 0) + return expand_extra_token(context, p[1], ret); + } + + for (colon=token+2; colon < token_end; colon++) + if (*colon == ':') + break; + + for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++) + if (!strncmp(token+2, tokens[i].tok, (colon - token) - 2)) { + char *arg = NULL; + + errcode = 0; + if (*colon == ':') { + int asprintf_ret = asprintf(&arg, "%.*s", + (int)(token_end - colon - 1), + colon + 1); + if (asprintf_ret < 0 || !arg) + errcode = ENOMEM; + } + if (!errcode) + errcode = tokens[i].exp_func(context, tokens[i].param, + tokens[i].postfix, arg, ret); + free(arg); + return errcode; + } + + heim_set_error_message(context, EINVAL, "Invalid token."); + return EINVAL; +} + +/** + * Internal function to expand tokens in paths. + * + * Params: + * + * @context A heim_context + * @path_in The path to expand tokens from + * @filepath True if this is a filesystem path (converts slashes to + * backslashes on Windows) + * @ppath_out The expanded path + * @... Variable number of pairs of strings, the first of each + * being a token (e.g., "luser") and the second a string to + * replace it with. The list is terminated by a NULL. + */ +heim_error_code +heim_expand_path_tokens(heim_context context, + const char *path_in, + int filepath, + char **ppath_out, + ...) +{ + heim_error_code ret; + va_list ap; + + va_start(ap, ppath_out); + ret = heim_expand_path_tokensv(context, path_in, filepath, ppath_out, ap); + va_end(ap); + + return ret; +} + +static void +free_extra_tokens(char **extra_tokens) +{ + char **p; + + for (p = extra_tokens; p && *p; p++) + free(*p); + free(extra_tokens); +} + +/** + * Internal function to expand tokens in paths. + * + * Inputs: + * + * @context A heim_context + * @path_in The path to expand tokens from + * @filepath True if this is a filesystem path (converts slashes to + * backslashes on Windows) + * @ppath_out The expanded path + * @ap A NULL-terminated va_list of pairs of strings, the first of each + * being a token (e.g., "luser") and the second a string to replace + * it with. + * + * Outputs: + * + * @ppath_out Path with expanded tokens (caller must free() this) + */ +heim_error_code +heim_expand_path_tokensv(heim_context context, + const char *path_in, + int filepath, + char **ppath_out, va_list ap) +{ + char *tok_begin, *tok_end, *append; + char **extra_tokens = NULL; + const char *path_left; + size_t nargs = 0; + size_t len = 0; + va_list ap2; + + if (path_in == NULL || *path_in == '\0') { + *ppath_out = strdup(""); + return 0; + } + + *ppath_out = NULL; + +#if defined(_MSC_VER) + ap2 = ap; /* Come on! See SO #558223 */ +#else + va_copy(ap2, ap); +#endif + while (va_arg(ap2, const char *)) { + nargs++; + va_arg(ap2, const char *); + } + va_end(ap2); + nargs *= 2; + + /* Get extra tokens */ + if (nargs) { + size_t i; + + extra_tokens = calloc(nargs + 1, sizeof (*extra_tokens)); + if (extra_tokens == NULL) + return heim_enomem(context); + for (i = 0; i < nargs; i++) { + const char *s = va_arg(ap, const char *); /* token key */ + if (s == NULL) + break; + extra_tokens[i] = strdup(s); + if (extra_tokens[i++] == NULL) { + free_extra_tokens(extra_tokens); + return heim_enomem(context); + } + s = va_arg(ap, const char *); /* token value */ + if (s == NULL) + s = ""; + extra_tokens[i] = strdup(s); + if (extra_tokens[i] == NULL) { + free_extra_tokens(extra_tokens); + return heim_enomem(context); + } + } + } + + for (path_left = path_in; path_left && *path_left; ) { + + tok_begin = strstr(path_left, "%{"); + + if (tok_begin && tok_begin != path_left) { + + append = malloc((tok_begin - path_left) + 1); + if (append) { + memcpy(append, path_left, tok_begin - path_left); + append[tok_begin - path_left] = '\0'; + } + path_left = tok_begin; + + } else if (tok_begin) { + + tok_end = strchr(tok_begin, '}'); + if (tok_end == NULL) { + free_extra_tokens(extra_tokens); + if (*ppath_out) + free(*ppath_out); + *ppath_out = NULL; + heim_set_error_message(context, EINVAL, "variable missing }"); + return EINVAL; + } + + if (expand_token(context, tok_begin, tok_end, extra_tokens, + &append)) { + free_extra_tokens(extra_tokens); + if (*ppath_out) + free(*ppath_out); + *ppath_out = NULL; + return EINVAL; + } + + path_left = tok_end + 1; + } else { + + append = strdup(path_left); + path_left = NULL; + + } + + if (append == NULL) { + + free_extra_tokens(extra_tokens); + if (*ppath_out) + free(*ppath_out); + *ppath_out = NULL; + return heim_enomem(context); + + } + + { + size_t append_len = strlen(append); + char * new_str = realloc(*ppath_out, len + append_len + 1); + + if (new_str == NULL) { + free_extra_tokens(extra_tokens); + free(append); + if (*ppath_out) + free(*ppath_out); + *ppath_out = NULL; + return heim_enomem(context); + } + + *ppath_out = new_str; + memcpy(*ppath_out + len, append, append_len + 1); + len = len + append_len; + free(append); + } + } + +#ifdef _WIN32 + /* Also deal with slashes */ + if (filepath && *ppath_out) { + char * c; + + for (c = *ppath_out; *c; c++) + if (*c == '/') + *c = '\\'; + } +#endif + + free_extra_tokens(extra_tokens); + return 0; +} diff --git a/third_party/heimdal/lib/base/heim_err.et b/third_party/heimdal/lib/base/heim_err.et new file mode 100644 index 0000000..5532d9b --- /dev/null +++ b/third_party/heimdal/lib/base/heim_err.et @@ -0,0 +1,57 @@ +# +# Error messages for the krb5 library +# +# This might look like a com_err file, but is not +# +id "$Id$" + +error_table heim + +prefix HEIM_ERR + +error_code LOG_PARSE, "Error parsing log destination" +error_code V4_PRINC_NO_CONV, "Failed to convert v4 principal" +error_code SALTTYPE_NOSUPP, "Salt type is not supported by enctype" +error_code NOHOST, "Host not found" +error_code OPNOTSUPP, "Operation not supported" +error_code EOF, "End of file" +error_code BAD_MKEY, "Failed to get the master key" +error_code SERVICE_NOMATCH, "Unacceptable service used" +error_code NOT_SEEKABLE, "File descriptor not seekable" +error_code TOO_BIG, "Offset too large" +error_code BAD_HDBENT_ENCODING, "Invalid HDB entry encoding" +error_code RANDOM_OFFLINE, "No random source available" +error_code CONFIG_BADFORMAT, "Improper format of configuration file" +error_code PA_CONTINUE_NEEDED, "Need to continue preauth stepping" +error_code PA_CANT_CONTINUE, "Can't continue with this preauth" +error_code NO_MORE_PA_MECHS, "No more PA mechanisms available" + +index 64 +prefix HEIM_PKINIT +error_code NO_CERTIFICATE, "Certificate missing" +error_code NO_PRIVATE_KEY, "Private key missing" +error_code NO_VALID_CA, "No valid certificate authority" +error_code CERTIFICATE_INVALID, "Certificate invalid" +error_code PRIVATE_KEY_INVALID, "Private key invalid" + +index 128 +prefix HEIM_EAI +#error_code NOERROR, "no error" +error_code UNKNOWN, "unknown error from getaddrinfo" +error_code ADDRFAMILY, "address family for nodename not supported" +error_code AGAIN, "temporary failure in name resolution" +error_code BADFLAGS, "invalid value for ai_flags" +error_code FAIL, "non-recoverable failure in name resolution" +error_code FAMILY, "ai_family not supported" +error_code MEMORY, "memory allocation failure" +error_code NODATA, "no address associated with nodename" +error_code NONAME, "nodename nor servname provided, or not known" +error_code SERVICE, "servname not supported for ai_socktype" +error_code SOCKTYPE, "ai_socktype not supported" +error_code SYSTEM, "system error returned in errno" + +index 192 +prefix HEIM_NET +error_code CONN_REFUSED, "connection refused" + +end diff --git a/third_party/heimdal/lib/base/heimbase-atomics.h b/third_party/heimdal/lib/base/heimbase-atomics.h new file mode 100644 index 0000000..e468a36 --- /dev/null +++ b/third_party/heimdal/lib/base/heimbase-atomics.h @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef HEIM_BASE_ATOMICS_H +#define HEIM_BASE_ATOMICS_H 1 + +#include + +/* + * Atomic operations + * + * (#define HEIM_BASE_ATOMICS_FALLBACK to test fallbacks.) + */ + +#if !defined(HEIM_BASE_ATOMICS_FALLBACK) && defined(HAVE_STDATOMIC_H) + +#include + +#define heim_base_atomic_init(t, v) atomic_init(t, v) +#define heim_base_atomic_load(x) atomic_load((x)) +#define heim_base_atomic_store(t, v) atomic_store((t), (v)) + +#define heim_base_atomic(T) _Atomic(T) + +#define heim_base_atomic_inc_32(x) (atomic_fetch_add((x), 1) + 1) +#define heim_base_atomic_dec_32(x) (atomic_fetch_sub((x), 1) - 1) +#define heim_base_atomic_inc_64(x) (atomic_fetch_add((x), 1) + 1) +#define heim_base_atomic_dec_64(x) (atomic_fetch_sub((x), 1) - 1) + +#define heim_base_exchange_pointer(t,v) atomic_exchange((t), (v)) +#define heim_base_exchange_32(t,v) atomic_exchange((t), (v)) +#define heim_base_exchange_64(t,v) atomic_exchange((t), (v)) + +/* + * 's and AIX's CAS functions take a pointer to an expected value + * and return a boolean, setting the pointed-to variable to the old value of + * the target. + * + * Other CAS functions, like GCC's, Solaris'/Illumos', and Windows', return the + * old value and don't take a pointer to an expected value. + * + * We implement the latter semantics. + */ +static inline void * +heim_base_cas_pointer_(heim_base_atomic(void *)*t, void *e, void *d) +{ + return atomic_compare_exchange_strong(t, &e, d), e; +} + +static inline uint32_t +heim_base_cas_32_(heim_base_atomic(uint32_t)*t, uint32_t e, uint32_t d) +{ + return atomic_compare_exchange_strong(t, &e, d), e; +} + +static inline uint64_t +heim_base_cas_64_(heim_base_atomic(uint64_t)*t, uint64_t e, uint64_t d) +{ + return atomic_compare_exchange_strong(t, &e, d), e; +} + +#define heim_base_cas_pointer(t,e,d) heim_base_cas_pointer_((t), (e), (d)) +#define heim_base_cas_32(t,e,d) heim_base_cas_32_((t), (e), (d)) +#define heim_base_cas_64(t,e,d) heim_base_cas_64_((t), (e), (d)) + +#elif !defined(HEIM_BASE_ATOMICS_FALLBACK) && defined(__GNUC__) && defined(HAVE___SYNC_ADD_AND_FETCH) + +#define heim_base_atomic_barrier() __sync_synchronize() + +#define heim_base_atomic_inc_32(x) __sync_add_and_fetch((x), 1) +#define heim_base_atomic_dec_32(x) __sync_sub_and_fetch((x), 1) +#define heim_base_atomic_inc_64(x) __sync_add_and_fetch((x), 1) +#define heim_base_atomic_dec_64(x) __sync_sub_and_fetch((x), 1) + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#if __has_builtin(__sync_swap) +#define heim_base_exchange_pointer(t,v) __sync_swap((t), (v)) +#else +/* FIXME: some targets may only write the value 1 into *t */ +#define heim_base_exchange_pointer(t,v) __sync_lock_test_and_set((t), (v)) +#endif + +#define heim_base_exchange_32(t,v) heim_base_exchange_pointer((t), (v)) +#define heim_base_exchange_64(t,v) heim_base_exchange_pointer((t), (v)) + +#define heim_base_cas_pointer(t,e,d) __sync_val_compare_and_swap((t), (e), (d)) +#define heim_base_cas_32(t,e,d) __sync_val_compare_and_swap((t), (e), (d)) +#define heim_base_cas_64(t,e,d) __sync_val_compare_and_swap((t), (e), (d)) + +#elif !defined(HEIM_BASE_ATOMICS_FALLBACK) && defined(__sun) + +#include +#include + +static inline void __heim_base_atomic_barrier(void) +{ + __machine_rw_barrier(); +} + +#define heim_base_atomic_barrier() __heim_base_atomic_barrier() + +#define heim_base_atomic(T) volatile T + +#define heim_base_atomic_inc_32(x) atomic_inc_32_nv((x)) +#define heim_base_atomic_dec_32(x) atomic_dec_32_nv((x)) +#define heim_base_atomic_inc_64(x) atomic_inc_64_nv((x)) +#define heim_base_atomic_dec_64(x) atomic_dec_64_nv((x)) + +#define heim_base_exchange_pointer(t,v) atomic_swap_ptr((t), (void *)(v)) +#define heim_base_exchange_32(t,v) atomic_swap_32((t), (v)) +#define heim_base_exchange_64(t,v) atomic_swap_64((t), (v)) + +#define heim_base_cas_pointer(t,e,d) atomic_cas_ptr((t), (e), (d)) +#define heim_base_cas_32(t,e,d) atomic_cas_32((t), (e), (d)) +#define heim_base_cas_64(t,e,d) atomic_cas_64((t), (e), (d)) + +#elif !defined(HEIM_BASE_ATOMICS_FALLBACK) && defined(_AIX) + +#include + +#define heim_base_atomic_barrier() __isync() + +#define heim_base_atomic_inc_32(x) (fetch_and_add((atomic_p)(x), 1) + 1) +#define heim_base_atomic_dec_32(x) (fetch_and_add((atomic_p)(x), -1) - 1) +#define heim_base_atomic_inc_64(x) (fetch_and_addlp((atomic_l)(x), 1) + 1) +#define heim_base_atomic_dec_64(x) (fetch_and_addlp((atomic_l)(x), -1) - 1) + +static inline void * +heim_base_exchange_pointer(void *p, void *newval) +{ + void *val = *(void **)p; + + while (!compare_and_swaplp((atomic_l)p, (long *)&val, (long)newval)) + ; + + return val; +} + +static inline uint32_t +heim_base_exchange_32(uint32_t *p, uint32_t newval) +{ + uint32_t val = *p; + + while (!compare_and_swap((atomic_p)p, (int *)&val, (int)newval)) + ; + + return val; +} + +static inline uint64_t +heim_base_exchange_64(uint64_t *p, uint64_t newval) +{ + uint64_t val = *p; + + while (!compare_and_swaplp((atomic_l)p, (long *)&val, (long)newval)) + ; + + return val; +} + +static inline void * +heim_base_cas_pointer_(heim_base_atomic(void *)*t, void *e, void *d) +{ + return compare_and_swaplp((atomic_l)t, &e, d), e; +} + +static inline uint32_t +heim_base_cas_32_(heim_base_atomic(uint32_t)*t, uint32_t e, uint32_t d) +{ + return compare_and_swap((atomic_p)t, &e, d), e; +} + +static inline uint64_t +heim_base_cas_64_(heim_base_atomic(uint64_t)*t, uint64_t e, uint64_t d) +{ + return compare_and_swaplp((atomic_l)t, &e, d), e; +} + +#define heim_base_cas_pointer(t,e,d) heim_base_cas_pointer_((t), (e), (d)) +#define heim_base_cas_32(t,e,d) heim_base_cas_32_((t), (e), (d)) +#define heim_base_cas_64(t,e,d) heim_base_cas_64_((t), (e), (d)) + +#elif !defined(HEIM_BASE_ATOMICS_FALLBACK) && defined(_WIN32) + +#define heim_base_atomic_barrier() MemoryBarrier() + +#define heim_base_atomic_inc_32(x) InterlockedIncrement(x) +#define heim_base_atomic_dec_32(x) InterlockedDecrement(x) +#define heim_base_atomic_inc_64(x) InterlockedIncrement64(x) +#define heim_base_atomic_dec_64(x) InterlockedDecrement64(x) + +#define heim_base_exchange_pointer(t,v) InterlockedExchangePointer((PVOID volatile *)(t), (PVOID)(v)) +#define heim_base_exchange_32(t,v) ((ULONG)InterlockedExchange((LONG volatile *)(t), (LONG)(v))) +#define heim_base_exchange_64(t,v) ((ULONG64)InterlockedExchange64((ULONG64 volatile *)(t), (LONG64)(v))) + +#define heim_base_cas_pointer(t,e,d) InterlockedCompareExchangePointer((PVOID volatile *)(t), (d), (e)) +#define heim_base_cas_32(t,e,d) InterlockedCompareExchange ((LONG volatile *)(t), (d), (e)) +#define heim_base_cas_64(t,e,d) InterlockedCompareExchange64((ULONG64 volatile *)(t), (d), (e)) + +#else + +#define heim_base_atomic(T) volatile T +#define heim_base_atomic_barrier() +#define heim_base_atomic_load(x) (*(x)) +#define heim_base_atomic_init(t, v) do { (*(t) = (v)); } while (0) +#define heim_base_atomic_store(t, v) do { (*(t) = (v)); } while (0) + +#include + +#define HEIM_BASE_NEED_ATOMIC_MUTEX 1 + +static inline uint32_t +heim_base_atomic_inc_32(heim_base_atomic(uint32_t) *x) +{ + uint32_t t; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + t = ++(*x); + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return t; +} + +static inline uint32_t +heim_base_atomic_dec_32(heim_base_atomic(uint32_t) *x) +{ + uint32_t t; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + t = --(*x); + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return t; +} + +static inline uint64_t +heim_base_atomic_inc_64(heim_base_atomic(uint64_t) *x) +{ + uint64_t t; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + t = ++(*x); + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return t; +} + +static inline uint64_t +heim_base_atomic_dec_64(heim_base_atomic(uint64_t) *x) +{ + uint64_t t; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + t = --(*x); + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return t; +} + +static inline void * +heim_base_exchange_pointer(heim_base_atomic(void *)target, void *value) +{ + void *old; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + old = *(void **)target; + *(void **)target = value; + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return old; +} + +static inline uint32_t +heim_base_exchange_32(heim_base_atomic(uint32_t) *target, uint32_t newval) +{ + uint32_t old; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + old = *target; + *target = newval; + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return old; +} + +static inline uint64_t +heim_base_exchange_64(heim_base_atomic(uint64_t) *target, uint64_t newval) +{ + uint64_t old; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + old = *target; + *target = newval; + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return old; +} + +static inline void * +heim_base_cas_pointer(heim_base_atomic(void *)target, void *expected, void *desired) +{ + void *old; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + if ((old = *(void **)target) == expected) + *(void **)target = desired; + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return old; +} + +static inline uint32_t +heim_base_cas_32(heim_base_atomic(uint32_t) *target, uint32_t expected, uint32_t desired) +{ + uint32_t old; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + if ((old = *(uint32_t *)target) == expected) + *target = desired; + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return old; +} + +static inline uint64_t +heim_base_cas_64(heim_base_atomic(uint64_t) *target, uint64_t expected,uint64_t desired) +{ + uint64_t old; + HEIMDAL_MUTEX_lock(heim_base_mutex()); + if ((old = *(uint64_t *)target) == expected) + *target = desired; + HEIMDAL_MUTEX_unlock(heim_base_mutex()); + return old; +} + +#endif /* defined(__GNUC__) && defined(HAVE___SYNC_ADD_AND_FETCH) */ + +#ifndef heim_base_atomic +#define heim_base_atomic(T) T +#endif + +#ifndef heim_base_atomic_barrier +static inline void heim_base_atomic_barrier(void) { return; } +#endif + +#ifndef heim_base_atomic_load +#define heim_base_atomic_load(x) (heim_base_atomic_barrier(), *(x)) +#endif + +#ifndef heim_base_atomic_init +#define heim_base_atomic_init(t, v) do { (*(t) = (v)); } while (0) +#endif + +#ifndef heim_base_atomic_store +#define heim_base_atomic_store(t, v) do { \ + (*(t) = (v)); \ + heim_base_atomic_barrier(); \ + } while (0) +#endif + +#if SIZEOF_TIME_T == 8 +#define heim_base_exchange_time_t(t,v) heim_base_exchange_64((t), (v)) +#elif SIZEOF_TIME_T == 4 +#define heim_base_exchange_time_t(t,v) heim_base_exchange_32((t), (v)) +#else +#error set SIZEOF_TIME_T for your platform +#endif + +#endif /* HEIM_BASE_ATOMICS_H */ diff --git a/third_party/heimdal/lib/base/heimbase-svc.h b/third_party/heimdal/lib/base/heimbase-svc.h new file mode 100644 index 0000000..6c2e02f --- /dev/null +++ b/third_party/heimdal/lib/base/heimbase-svc.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef HEIMBASE_SVC_H +#define HEIMBASE_SVC_H 1 + +#include + +/* + * This file is meant to be included in services, which can + * + * #define heim_pcontext krb5_context + * + * or whatever is appropriate. + */ + +#define HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS \ + /* Input */ \ + heim_pcontext context; \ + heim_pconfig config; \ + heim_context hcontext; \ + heim_log_facility *logf; \ + const char *from; \ + struct sockaddr *addr; \ + int datagram_reply; \ + heim_octet_string request; \ + \ + /* Output */ \ + heim_octet_string *reply; \ + unsigned int use_request_t:1; \ + \ + /* Common state, to be freed in process.c */ \ + struct timeval tv_start; \ + struct timeval tv_end; \ + const char *reqtype; \ + char *cname; \ + char *sname; \ + const char *e_text; \ + heim_octet_string e_data; \ + char *e_text_buf; \ + heim_string_t reason; \ + /* auditing key/value store */ \ + heim_dict_t kv; \ + heim_dict_t attributes; \ + int32_t error_code + +#define HEIM_PLUGIN_FTABLE_COMMON_ELEMENTS(CONTEXT_TYPE) \ + int minor_version; \ + int (HEIM_LIB_CALL *init)(CONTEXT_TYPE, void **); \ + void (HEIM_LIB_CALL *fini)(void *) + +#endif /* HEIMBASE_SVC_H */ diff --git a/third_party/heimdal/lib/base/heimbase.c b/third_party/heimdal/lib/base/heimbase.c new file mode 100644 index 0000000..4e9ea1b --- /dev/null +++ b/third_party/heimdal/lib/base/heimbase.c @@ -0,0 +1,1104 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" +#include "heimbase-atomics.h" +#include + +static heim_base_atomic(uint32_t) tidglobal = HEIM_TID_USER; + +struct heim_base { + heim_const_type_t isa; + heim_base_atomic(uint32_t) ref_cnt; + HEIM_TAILQ_ENTRY(heim_base) autorel; + heim_auto_release_t autorelpool; + uintptr_t isaextra[3]; +}; + +/* specialized version of base */ +struct heim_base_mem { + heim_const_type_t isa; + heim_base_atomic(uint32_t) ref_cnt; + HEIM_TAILQ_ENTRY(heim_base) autorel; + heim_auto_release_t autorelpool; + const char *name; + void (HEIM_CALLCONV *dealloc)(void *); + uintptr_t isaextra[1]; +}; + +#define PTR2BASE(ptr) (((struct heim_base *)ptr) - 1) +#define BASE2PTR(ptr) ((void *)(((struct heim_base *)ptr) + 1)) + +HEIMDAL_MUTEX * HEIM_CALLCONV +heim_base_mutex(void) +{ + static HEIMDAL_MUTEX _heim_base_mutex = HEIMDAL_MUTEX_INITIALIZER; + return &_heim_base_mutex; +} + +/* + * Auto release structure + */ + +struct heim_auto_release { + HEIM_TAILQ_HEAD(, heim_base) pool; + HEIMDAL_MUTEX pool_mutex; + struct heim_auto_release *parent; +}; + + +/** + * Retain object (i.e., take a reference) + * + * @param object to be released, NULL is ok + * + * @return the same object as passed in + */ + +heim_object_t +heim_retain(heim_object_t ptr) +{ + struct heim_base *p; + + if (ptr == NULL || heim_base_is_tagged(ptr)) + return ptr; + + p = PTR2BASE(ptr); + + if (heim_base_atomic_load(&p->ref_cnt) == UINT32_MAX) + return ptr; + + if ((heim_base_atomic_inc_32(&p->ref_cnt) - 1) == 0) + heim_abort("resurection"); + return ptr; +} + +/** + * Release object, free if reference count reaches zero + * + * @param object to be released + */ + +void +heim_release(void *ptr) +{ + heim_base_atomic(uint32_t) old; + struct heim_base *p; + + if (ptr == NULL || heim_base_is_tagged(ptr)) + return; + + p = PTR2BASE(ptr); + + if (heim_base_atomic_load(&p->ref_cnt) == UINT32_MAX) + return; + + old = heim_base_atomic_dec_32(&p->ref_cnt) + 1; + + if (old > 1) + return; + + if (old == 1) { + heim_auto_release_t ar = p->autorelpool; + /* remove from autorel pool list */ + if (ar) { + p->autorelpool = NULL; + HEIMDAL_MUTEX_lock(&ar->pool_mutex); + HEIM_TAILQ_REMOVE(&ar->pool, p, autorel); + HEIMDAL_MUTEX_unlock(&ar->pool_mutex); + } + if (p->isa->dealloc) + p->isa->dealloc(ptr); + free(p); + } else + heim_abort("over release"); +} + +/** + * If used require wrapped in autorelease pool + */ + +heim_string_t +heim_description(heim_object_t ptr) +{ + struct heim_base *p = PTR2BASE(ptr); + if (p->isa->desc == NULL) + return heim_auto_release(heim_string_ref_create(p->isa->name, NULL)); + return heim_auto_release(p->isa->desc(ptr)); +} + + +void +_heim_make_permanent(heim_object_t ptr) +{ + struct heim_base *p = PTR2BASE(ptr); + heim_base_atomic_store(&p->ref_cnt, UINT32_MAX); +} + + +static heim_type_t tagged_isa[9] = { + &_heim_number_object, + &_heim_null_object, + &_heim_bool_object, + + NULL, + NULL, + NULL, + + NULL, + NULL, + NULL +}; + +heim_const_type_t +_heim_get_isa(heim_object_t ptr) +{ + struct heim_base *p; + if (heim_base_is_tagged(ptr)) { + if (heim_base_is_tagged_object(ptr)) + return tagged_isa[heim_base_tagged_object_tid(ptr)]; + heim_abort("not a supported tagged type"); + } + p = PTR2BASE(ptr); + return p->isa; +} + +/** + * Get type ID of object + * + * @param object object to get type id of + * + * @return type id of object + */ + +heim_tid_t +heim_get_tid(heim_object_t ptr) +{ + heim_const_type_t isa = _heim_get_isa(ptr); + return isa->tid; +} + +/** + * Get hash value of object + * + * @param object object to get hash value for + * + * @return a hash value + */ + +uintptr_t +heim_get_hash(heim_object_t ptr) +{ + heim_const_type_t isa = _heim_get_isa(ptr); + if (isa->hash) + return isa->hash(ptr); + return (uintptr_t)ptr; +} + +/** + * Compare two objects, returns 0 if equal, can use used for qsort() + * and friends. + * + * @param a first object to compare + * @param b first object to compare + * + * @return 0 if objects are equal + */ + +int +heim_cmp(heim_object_t a, heim_object_t b) +{ + heim_tid_t ta, tb; + heim_const_type_t isa; + + ta = heim_get_tid(a); + tb = heim_get_tid(b); + + if (ta != tb) + return ta - tb; + + isa = _heim_get_isa(a); + + if (isa->cmp) + return isa->cmp(a, b); + + return (uintptr_t)a - (uintptr_t)b; +} + +/* + * Private - allocates an memory object + */ + +static void HEIM_CALLCONV +memory_dealloc(void *ptr) +{ + if (ptr) { + struct heim_base_mem *p = (struct heim_base_mem *)PTR2BASE(ptr); + + if (p->dealloc) + p->dealloc(ptr); + } +} + +static const struct heim_type_data memory_object = { + HEIM_TID_MEMORY, + "memory-object", + NULL, + memory_dealloc, + NULL, + NULL, + NULL, + NULL +}; + +/** + * Allocate memory for an object of anonymous type + * + * @param size size of object to be allocated + * @param name name of ad-hoc type + * @param dealloc destructor function + * + * Objects allocated with this interface do not serialize. + * + * @return allocated object + */ + +void * +heim_alloc(size_t size, const char *name, heim_type_dealloc dealloc) +{ + /* XXX use posix_memalign */ + + struct heim_base_mem *p = calloc(1, size + sizeof(*p)); + if (p == NULL) + return NULL; + p->isa = &memory_object; + p->ref_cnt = 1; + p->name = name; + p->dealloc = dealloc; + return BASE2PTR(p); +} + +heim_type_t +_heim_create_type(const char *name, + heim_type_init init, + heim_type_dealloc dealloc, + heim_type_copy copy, + heim_type_cmp cmp, + heim_type_hash hash, + heim_type_description desc) +{ + heim_type_t type; + + type = calloc(1, sizeof(*type)); + if (type == NULL) + return NULL; + + type->tid = heim_base_atomic_inc_32(&tidglobal); + type->name = name; + type->init = init; + type->dealloc = dealloc; + type->copy = copy; + type->cmp = cmp; + type->hash = hash; + type->desc = desc; + + return type; +} + +heim_object_t +_heim_alloc_object(heim_const_type_t type, size_t size) +{ + /* XXX should use posix_memalign */ + struct heim_base *p = calloc(1, size + sizeof(*p)); + if (p == NULL) + return NULL; + p->isa = type; + p->ref_cnt = 1; + + return BASE2PTR(p); +} + +void * +_heim_get_isaextra(heim_object_t ptr, size_t idx) +{ + struct heim_base *p; + + heim_assert(ptr != NULL, "internal error"); + p = (struct heim_base *)PTR2BASE(ptr); + if (p->isa == &memory_object) + return NULL; + heim_assert(idx < 3, "invalid private heim_base extra data index"); + return &p->isaextra[idx]; +} + +heim_tid_t +_heim_type_get_tid(heim_type_t type) +{ + return type->tid; +} + +#if !defined(WIN32) && !defined(HAVE_DISPATCH_DISPATCH_H) && defined(ENABLE_PTHREAD_SUPPORT) +static pthread_once_t once_arg_key_once = PTHREAD_ONCE_INIT; +static pthread_key_t once_arg_key; + +static void +once_arg_key_once_init(void) +{ + errno = pthread_key_create(&once_arg_key, NULL); + if (errno != 0) { + fprintf(stderr, + "Error: pthread_key_create() failed, cannot continue: %s\n", + strerror(errno)); + abort(); + } +} + +struct once_callback { + void (*fn)(void *); + void *data; +}; + +static void +once_callback_caller(void) +{ + struct once_callback *once_callback = pthread_getspecific(once_arg_key); + + if (once_callback == NULL) { + fprintf(stderr, "Error: pthread_once() calls callback on " + "different thread?! Cannot continue.\n"); + abort(); + } + once_callback->fn(once_callback->data); +} +#endif + +/** + * Call func once and only once + * + * @param once pointer to a heim_base_once_t + * @param ctx context passed to func + * @param func function to be called + */ + +void +heim_base_once_f(heim_base_once_t *once, void *ctx, void (*func)(void *)) +{ +#if defined(WIN32) + /* + * With a libroken wrapper for some CAS function and a libroken yield() + * wrapper we could make this the default implementation when we have + * neither Grand Central nor POSX threads. + * + * We could also adapt the double-checked lock pattern with CAS + * providing the necessary memory barriers in the absence of + * portable explicit memory barrier APIs. + */ + /* + * We use CAS operations in large part to provide implied memory + * barriers. + * + * State 0 means that func() has never executed. + * State 1 means that func() is executing. + * State 2 means that func() has completed execution. + */ + if (InterlockedCompareExchange(once, 1L, 0L) == 0L) { + /* State is now 1 */ + (*func)(ctx); + (void)InterlockedExchange(once, 2L); + /* State is now 2 */ + } else { + /* + * The InterlockedCompareExchange is being used to fetch + * the current state under a full memory barrier. As long + * as the current state is 1 continue to spin. + */ + while (InterlockedCompareExchange(once, 2L, 0L) == 1L) + SwitchToThread(); + } +#elif defined(HAVE_DISPATCH_DISPATCH_H) + dispatch_once_f(once, ctx, func); +#elif defined(ENABLE_PTHREAD_SUPPORT) + struct once_callback once_callback; + + once_callback.fn = func; + once_callback.data = ctx; + + errno = pthread_once(&once_arg_key_once, once_arg_key_once_init); + if (errno != 0) { + fprintf(stderr, "Error: pthread_once() failed, cannot continue: %s\n", + strerror(errno)); + abort(); + } + errno = pthread_setspecific(once_arg_key, &once_callback); + if (errno != 0) { + fprintf(stderr, + "Error: pthread_setspecific() failed, cannot continue: %s\n", + strerror(errno)); + abort(); + } + errno = pthread_once(once, once_callback_caller); + if (errno != 0) { + fprintf(stderr, "Error: pthread_once() failed, cannot continue: %s\n", + strerror(errno)); + abort(); + } +#else + static HEIMDAL_MUTEX mutex = HEIMDAL_MUTEX_INITIALIZER; + HEIMDAL_MUTEX_lock(&mutex); + if (*once == 0) { + *once = 1; + HEIMDAL_MUTEX_unlock(&mutex); + func(ctx); + HEIMDAL_MUTEX_lock(&mutex); + *once = 2; + HEIMDAL_MUTEX_unlock(&mutex); + } else if (*once == 2) { + HEIMDAL_MUTEX_unlock(&mutex); + } else { + HEIMDAL_MUTEX_unlock(&mutex); + while (1) { + struct timeval tv = { 0, 1000 }; + select(0, NULL, NULL, NULL, &tv); + HEIMDAL_MUTEX_lock(&mutex); + if (*once == 2) + break; + HEIMDAL_MUTEX_unlock(&mutex); + } + HEIMDAL_MUTEX_unlock(&mutex); + } +#endif +} + +/** + * Abort and log the failure (using syslog) + */ + +void +heim_abort(const char *fmt, ...) + HEIMDAL_NORETURN_ATTRIBUTE + HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 1, 2)) +{ + va_list ap; + va_start(ap, fmt); + heim_abortv(fmt, ap); + va_end(ap); +} + +/** + * Abort and log the failure (using syslog) + */ + +void +heim_abortv(const char *fmt, va_list ap) + HEIMDAL_NORETURN_ATTRIBUTE + HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 1, 0)) +{ + static char str[1024]; + + vsnprintf(str, sizeof(str), fmt, ap); + syslog(LOG_ERR, "heim_abort: %s", str); + abort(); +} + +/* + * + */ + +static int ar_created = 0; +static HEIMDAL_thread_key ar_key; + +struct ar_tls { + struct heim_auto_release *head; + struct heim_auto_release *current; + HEIMDAL_MUTEX tls_mutex; +}; + +static void +ar_tls_delete(void *ptr) +{ + struct ar_tls *tls = ptr; + heim_auto_release_t next = NULL; + + if (tls == NULL) + return; + for (; tls->current != NULL; tls->current = next) { + next = tls->current->parent; + heim_release(tls->current); + } + free(tls); +} + +static void +init_ar_tls(void *ptr) +{ + int ret; + HEIMDAL_key_create(&ar_key, ar_tls_delete, ret); + if (ret == 0) + ar_created = 1; +} + +static struct ar_tls * +autorel_tls(void) +{ + static heim_base_once_t once = HEIM_BASE_ONCE_INIT; + struct ar_tls *arp; + int ret; + + heim_base_once_f(&once, NULL, init_ar_tls); + if (!ar_created) + return NULL; + + arp = HEIMDAL_getspecific(ar_key); + if (arp == NULL) { + + arp = calloc(1, sizeof(*arp)); + if (arp == NULL) + return NULL; + HEIMDAL_setspecific(ar_key, arp, ret); + if (ret) { + free(arp); + return NULL; + } + } + return arp; + +} + +static void HEIM_CALLCONV +autorel_dealloc(void *ptr) +{ + heim_auto_release_t ar = ptr; + struct ar_tls *tls; + + tls = autorel_tls(); + if (tls == NULL) + heim_abort("autorelease pool released on thread w/o autorelease inited"); + + heim_auto_release_drain(ar); + + if (!HEIM_TAILQ_EMPTY(&ar->pool)) + heim_abort("pool not empty after draining"); + + HEIMDAL_MUTEX_lock(&tls->tls_mutex); + if (tls->current != ptr) + heim_abort("autorelease not releaseing top pool"); + + tls->current = ar->parent; + HEIMDAL_MUTEX_unlock(&tls->tls_mutex); +} + +static int +autorel_cmp(void *a, void *b) +{ + return (a == b); +} + +static uintptr_t +autorel_hash(void *ptr) +{ + return (uintptr_t)ptr; +} + + +static struct heim_type_data _heim_autorel_object = { + HEIM_TID_AUTORELEASE, + "autorelease-pool", + NULL, + autorel_dealloc, + NULL, + autorel_cmp, + autorel_hash, + NULL +}; + +/** + * Create thread-specific object auto-release pool + * + * Objects placed on the per-thread auto-release pool (with + * heim_auto_release()) can be released in one fell swoop by calling + * heim_auto_release_drain(). + */ + +heim_auto_release_t +heim_auto_release_create(void) +{ + struct ar_tls *tls = autorel_tls(); + heim_auto_release_t ar; + + if (tls == NULL) + heim_abort("Failed to create/get autorelease head"); + + ar = _heim_alloc_object(&_heim_autorel_object, sizeof(struct heim_auto_release)); + if (ar) { + HEIMDAL_MUTEX_lock(&tls->tls_mutex); + if (tls->head == NULL) + tls->head = ar; + ar->parent = tls->current; + tls->current = ar; + HEIMDAL_MUTEX_unlock(&tls->tls_mutex); + } + + return ar; +} + +/** + * Place the current object on the thread's auto-release pool + * + * @param ptr object + */ + +heim_object_t +heim_auto_release(heim_object_t ptr) +{ + struct heim_base *p; + struct ar_tls *tls; + heim_auto_release_t ar; + + if (ptr == NULL || heim_base_is_tagged(ptr)) + return ptr; + + p = PTR2BASE(ptr); + tls = autorel_tls(); + + /* drop from old pool */ + if ((ar = p->autorelpool) != NULL) { + HEIMDAL_MUTEX_lock(&ar->pool_mutex); + HEIM_TAILQ_REMOVE(&ar->pool, p, autorel); + p->autorelpool = NULL; + HEIMDAL_MUTEX_unlock(&ar->pool_mutex); + } + + if (tls == NULL || (ar = tls->current) == NULL) + heim_abort("no auto relase pool in place, would leak"); + + HEIMDAL_MUTEX_lock(&ar->pool_mutex); + HEIM_TAILQ_INSERT_HEAD(&ar->pool, p, autorel); + p->autorelpool = ar; + HEIMDAL_MUTEX_unlock(&ar->pool_mutex); + + return ptr; +} + +/** + * Release all objects on the given auto-release pool + */ + +void +heim_auto_release_drain(heim_auto_release_t autorel) +{ + heim_object_t obj; + + /* release all elements on the tail queue */ + + HEIMDAL_MUTEX_lock(&autorel->pool_mutex); + while(!HEIM_TAILQ_EMPTY(&autorel->pool)) { + obj = HEIM_TAILQ_FIRST(&autorel->pool); + HEIMDAL_MUTEX_unlock(&autorel->pool_mutex); + heim_release(BASE2PTR(obj)); + HEIMDAL_MUTEX_lock(&autorel->pool_mutex); + } + HEIMDAL_MUTEX_unlock(&autorel->pool_mutex); +} + +/* + * Helper for heim_path_vget() and heim_path_delete(). On success + * outputs the node named by the path and the parent node and key + * (useful for heim_path_delete()). + */ + +static heim_object_t +heim_path_vget2(heim_object_t ptr, heim_object_t *parent, heim_object_t *key, + heim_error_t *error, va_list ap) +{ + heim_object_t path_element; + heim_object_t node, next_node; + heim_tid_t node_type; + + *parent = NULL; + *key = NULL; + if (ptr == NULL) + return NULL; + + for (node = ptr; node != NULL; ) { + path_element = va_arg(ap, heim_object_t); + if (path_element == NULL) { + *parent = node; + *key = path_element; + return node; + } + + node_type = heim_get_tid(node); + switch (node_type) { + case HEIM_TID_ARRAY: + case HEIM_TID_DICT: + case HEIM_TID_DB: + break; + default: + if (node == ptr) + heim_abort("heim_path_get() only operates on container types"); + return NULL; + } + + if (node_type == HEIM_TID_DICT) { + next_node = heim_dict_get_value(node, path_element); + } else if (node_type == HEIM_TID_DB) { + next_node = _heim_db_get_value(node, NULL, path_element, NULL); + } else { + int idx = -1; + + /* node_type == HEIM_TID_ARRAY */ + if (heim_get_tid(path_element) == HEIM_TID_NUMBER) + idx = heim_number_get_int(path_element); + if (idx < 0) { + if (error) + *error = heim_error_create(EINVAL, + "heim_path_get() path elements " + "for array nodes must be " + "numeric and positive"); + return NULL; + } + next_node = heim_array_get_value(node, idx); + } + node = next_node; + } + return NULL; +} + +/** + * Get a node in a heim_object tree by path + * + * @param ptr tree + * @param error error (output) + * @param ap NULL-terminated va_list of heim_object_ts that form a path + * + * @return object (not retained) if found + * + * @addtogroup heimbase + */ + +heim_object_t +heim_path_vget(heim_object_t ptr, heim_error_t *error, va_list ap) +{ + heim_object_t p, k; + + return heim_path_vget2(ptr, &p, &k, error, ap); +} + +/** + * Get a node in a tree by path, with retained reference + * + * @param ptr tree + * @param error error (output) + * @param ap NULL-terminated va_list of heim_object_ts that form a path + * + * @return retained object if found + * + * @addtogroup heimbase + */ + +heim_object_t +heim_path_vcopy(heim_object_t ptr, heim_error_t *error, va_list ap) +{ + heim_object_t p, k; + + return heim_retain(heim_path_vget2(ptr, &p, &k, error, ap)); +} + +/** + * Get a node in a tree by path + * + * @param ptr tree + * @param error error (output) + * @param ... NULL-terminated va_list of heim_object_ts that form a path + * + * @return object (not retained) if found + * + * @addtogroup heimbase + */ + +heim_object_t +heim_path_get(heim_object_t ptr, heim_error_t *error, ...) +{ + heim_object_t o; + heim_object_t p, k; + va_list ap; + + if (ptr == NULL) + return NULL; + + va_start(ap, error); + o = heim_path_vget2(ptr, &p, &k, error, ap); + va_end(ap); + return o; +} + +/** + * Get a node in a tree by path, with retained reference + * + * @param ptr tree + * @param error error (output) + * @param ... NULL-terminated va_list of heim_object_ts that form a path + * + * @return retained object if found + * + * @addtogroup heimbase + */ + +heim_object_t +heim_path_copy(heim_object_t ptr, heim_error_t *error, ...) +{ + heim_object_t o; + heim_object_t p, k; + va_list ap; + + if (ptr == NULL) + return NULL; + + va_start(ap, error); + o = heim_retain(heim_path_vget2(ptr, &p, &k, error, ap)); + va_end(ap); + return o; +} + +/** + * Create a path in a heim_object_t tree + * + * @param ptr the tree + * @param size the size of the heim_dict_t nodes to be created + * @param leaf leaf node to be added, if any + * @param error error (output) + * @param ap NULL-terminated of path component objects + * + * Create a path of heim_dict_t interior nodes in a given heim_object_t + * tree, as necessary, and set/replace a leaf, if given (if leaf is NULL + * then the leaf is not deleted). + * + * @return 0 on success, else a system error + * + * @addtogroup heimbase + */ + +int +heim_path_vcreate(heim_object_t ptr, size_t size, heim_object_t leaf, + heim_error_t *error, va_list ap) +{ + heim_object_t path_element = va_arg(ap, heim_object_t); + heim_object_t next_path_element = NULL; + heim_object_t node = ptr; + heim_object_t next_node = NULL; + heim_tid_t node_type; + int ret = 0; + + if (ptr == NULL) + heim_abort("heim_path_vcreate() does not create root nodes"); + + while (path_element != NULL) { + int idx = -1; + + next_path_element = va_arg(ap, heim_object_t); + node_type = heim_get_tid(node); + + if (node_type == HEIM_TID_DICT) { + next_node = heim_dict_get_value(node, path_element); + } else if (node_type == HEIM_TID_ARRAY) { + if (heim_get_tid(path_element) == HEIM_TID_NUMBER) + idx = heim_number_get_int(path_element); + if (idx < 0) { + if (error) + *error = heim_error_create(EINVAL, + "heim_path() path elements for " + "array nodes must be numeric " + "and positive"); + return EINVAL; + } + if (idx < heim_array_get_length(node)) { + next_node = heim_array_get_value(node, idx); + } else if (idx == heim_array_get_length(node)) { + next_node = NULL; + } else { + if (error) + *error = heim_error_create(EINVAL, + "Index for array in path is too large"); + return EINVAL; + } + } else if (node_type == HEIM_TID_DB && next_path_element != NULL) { + if (error) + *error = heim_error_create(EINVAL, "Interior node is a DB"); + return EINVAL; + } + + if (next_path_element == NULL) + break; + + /* Create missing interior node */ + if (next_node == NULL) { + heim_dict_t new_node; + + new_node = heim_dict_create(size); /* no arrays or DBs, just dicts */ + if (new_node == NULL) { + ret = ENOMEM; + goto err; + } + + if (node_type == HEIM_TID_DICT) { + ret = heim_dict_set_value(node, path_element, new_node); + next_node = heim_dict_get_value(node, path_element); + } else if (node_type == HEIM_TID_ARRAY && + heim_number_get_int(path_element) <= heim_array_get_length(node)) { + ret = heim_array_insert_value(node, + heim_number_get_int(path_element), + new_node); + next_node = heim_array_get_value(node, idx); + } else { + ret = EINVAL; + if (error) + *error = heim_error_create(ret, "Node in path not a " + "container"); + } + + heim_release(new_node); + if (ret) + goto err; + } + + path_element = next_path_element; + node = next_node; + next_node = NULL; + } + + if (path_element == NULL) + goto err; + + /* Add the leaf */ + if (leaf != NULL) { + if (node_type == HEIM_TID_DICT) + ret = heim_dict_set_value(node, path_element, leaf); + else + ret = heim_array_insert_value(node, + heim_number_get_int(path_element), + leaf); + } + return ret; + +err: + if (error && !*error) { + if (ret == ENOMEM) + *error = heim_error_create_enomem(); + else + *error = heim_error_create(ret, "Could not set " + "dict value"); + } + return ret; +} + +/** + * Create a path in a heim_object_t tree + * + * @param ptr the tree + * @param size the size of the heim_dict_t nodes to be created + * @param leaf leaf node to be added, if any + * @param error error (output) + * @param ... NULL-terminated list of path component objects + * + * Create a path of heim_dict_t interior nodes in a given heim_object_t + * tree, as necessary, and set/replace a leaf, if given (if leaf is NULL + * then the leaf is not deleted). + * + * @return 0 on success, else a system error + * + * @addtogroup heimbase + */ + +int +heim_path_create(heim_object_t ptr, size_t size, heim_object_t leaf, + heim_error_t *error, ...) +{ + va_list ap; + int ret; + + va_start(ap, error); + ret = heim_path_vcreate(ptr, size, leaf, error, ap); + va_end(ap); + return ret; +} + +/** + * Delete leaf node named by a path in a heim_object_t tree + * + * @param ptr the tree + * @param error error (output) + * @param ap NULL-terminated list of path component objects + * + * @addtogroup heimbase + */ + +void +heim_path_vdelete(heim_object_t ptr, heim_error_t *error, va_list ap) +{ + heim_object_t parent, key, child; + + child = heim_path_vget2(ptr, &parent, &key, error, ap); + if (child != NULL) { + if (heim_get_tid(parent) == HEIM_TID_DICT) + heim_dict_delete_key(parent, key); + else if (heim_get_tid(parent) == HEIM_TID_DB) + heim_db_delete_key(parent, NULL, key, error); + else if (heim_get_tid(parent) == HEIM_TID_ARRAY) + heim_array_delete_value(parent, heim_number_get_int(key)); + heim_release(child); + } +} + +/** + * Delete leaf node named by a path in a heim_object_t tree + * + * @param ptr the tree + * @param error error (output) + * @param ap NULL-terminated list of path component objects + * + * @addtogroup heimbase + */ + +void +heim_path_delete(heim_object_t ptr, heim_error_t *error, ...) +{ + va_list ap; + + va_start(ap, error); + heim_path_vdelete(ptr, error, ap); + va_end(ap); + return; +} + diff --git a/third_party/heimdal/lib/base/heimbase.h b/third_party/heimdal/lib/base/heimbase.h new file mode 100644 index 0000000..4546df9 --- /dev/null +++ b/third_party/heimdal/lib/base/heimbase.h @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef HEIM_BASE_H +#define HEIM_BASE_H 1 + +#include +#ifndef _WIN32 +#include +#endif +#if !defined(WIN32) && !defined(HAVE_DISPATCH_DISPATCH_H) && defined(ENABLE_PTHREAD_SUPPORT) +#include +#endif +#include +#include +#ifdef _WIN32 +#include +#endif +#ifdef HAVE_STDBOOL_H +#include +#else +#ifndef false +#define false 0 +#endif +#ifndef true +#define true 1 +#endif +#endif + +#include + +#include + +#ifdef _WIN32 +#define HEIM_CALLCONV __stdcall +#define HEIM_LIB_CALL __stdcall +#else +#define HEIM_CALLCONV +#define HEIM_LIB_CALL +#endif + +#if !defined(__GNUC__) && !defined(__attribute__) +#define __attribute__(x) +#endif + +#define HEIM_BASE_API_VERSION 20130210 + +/* + * Generic facilities (moved from lib/krb5/. + */ + +typedef int32_t heim_error_code; +typedef struct heim_context_s *heim_context; +typedef struct heim_pcontext_s *heim_pcontext; + +typedef void (HEIM_CALLCONV *heim_log_log_func_t)(heim_context, + const char *, + const char *, + void *); +typedef void (HEIM_CALLCONV *heim_log_close_func_t)(void *); + +typedef struct heim_log_facility_s heim_log_facility; + +typedef uintptr_t +(HEIM_LIB_CALL *heim_get_instance_func_t)(const char *); + +#define HEIM_PLUGIN_INVOKE_ALL 1 + +struct heim_plugin_data { + const char *module; + const char *name; + int min_version; + const char *const *deps; + heim_get_instance_func_t get_instance; +}; + +/* + * heim_config_binding is identical to struct krb5_config_binding + * within krb5.h. Its format is public and used by callers of + * krb5_config_get_list() and krb5_config_vget_list(). + */ +enum heim_config_type { + heim_config_string, + heim_config_list, +}; +struct heim_config_binding { + enum heim_config_type type; + char *name; + struct heim_config_binding *next; + union { + char *string; + struct heim_config_binding *list; + void *generic; + } u; +}; +typedef struct heim_config_binding heim_config_binding; +typedef struct heim_config_binding heim_config_section; + +/* + * CF-like, JSON APIs + */ + +typedef enum heim_tid_enum { + HEIM_TID_NUMBER = 0, + HEIM_TID_NULL = 1, + HEIM_TID_BOOL = 2, + HEIM_TID_TAGGED_UNUSED2 = 3, /* reserved for tagged object types */ + HEIM_TID_TAGGED_UNUSED3 = 4, /* reserved for tagged object types */ + HEIM_TID_TAGGED_UNUSED4 = 5, /* reserved for tagged object types */ + HEIM_TID_TAGGED_UNUSED5 = 6, /* reserved for tagged object types */ + HEIM_TID_TAGGED_UNUSED6 = 7, /* reserved for tagged object types */ + HEIM_TID_MEMORY = 128, + HEIM_TID_ARRAY = 129, + HEIM_TID_DICT = 130, + HEIM_TID_STRING = 131, + HEIM_TID_AUTORELEASE = 132, + HEIM_TID_ERROR = 133, + HEIM_TID_DATA = 134, + HEIM_TID_DB = 135, + HEIM_TID_PA_AUTH_MECH = 136, + HEIM_TID_PAC = 137, + HEIM_TID_USER = 255 +} heim_tid; + +typedef void * heim_object_t; +typedef unsigned int heim_tid_t; +typedef heim_object_t heim_bool_t; +typedef heim_object_t heim_null_t; +#ifdef WIN32 +typedef LONG heim_base_once_t; +#define HEIM_BASE_ONCE_INIT 0 +#elif defined(HAVE_DISPATCH_DISPATCH_H) +typedef long heim_base_once_t; /* XXX arch dependant */ +#define HEIM_BASE_ONCE_INIT 0 +#elif defined(ENABLE_PTHREAD_SUPPORT) +typedef pthread_once_t heim_base_once_t; +#define HEIM_BASE_ONCE_INIT PTHREAD_ONCE_INIT +#else +typedef long heim_base_once_t; /* XXX arch dependant */ +#define HEIM_BASE_ONCE_INIT 0 +#endif + +#if !defined(__has_extension) +#define __has_extension(x) 0 +#endif + +#define HEIM_REQUIRE_GNUC(m,n,p) \ + (((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100) + __GNUC_PATCHLEVEL__) >= \ + (((m) * 10000) + ((n) * 100) + (p))) + + +#if __has_extension(__builtin_expect) || HEIM_REQUIRE_GNUC(3,0,0) +#define heim_builtin_expect(_op,_res) __builtin_expect(_op,_res) +#else +#define heim_builtin_expect(_op,_res) (_op) +#endif + + +typedef void (HEIM_CALLCONV *heim_type_dealloc)(void *); + +#define heim_assert(e,t) \ + (heim_builtin_expect(!(e), 0) ? heim_abort(t ":" #e) : (void)0) + +/* + * + */ + +/* + * Array + */ + +typedef struct heim_array_data *heim_array_t; + +typedef void (*heim_array_iterator_f_t)(heim_object_t, void *, int *); +typedef int (*heim_array_filter_f_t)(heim_object_t, void *); + +/* + * Dict + */ + +typedef struct heim_dict_data *heim_dict_t; + +typedef void (*heim_dict_iterator_f_t)(heim_object_t, heim_object_t, void *); + +/* + * String + */ + +typedef struct heim_string_data *heim_string_t; +typedef void (*heim_string_free_f_t)(void *); + +#define HSTR(_str) (__heim_string_constant("" _str "")) +heim_string_t __heim_string_constant(const char *); + +/* + * Errors + */ + +typedef struct heim_error * heim_error_t; + +/* + * Path + */ + +/* + * Data (octet strings) + */ + +#ifndef __HEIM_BASE_DATA__ +#define __HEIM_BASE_DATA__ +struct heim_base_data { + size_t length; + void *data; +}; +typedef struct heim_base_data heim_octet_string; +#endif + +typedef struct heim_base_data * heim_data_t; +typedef void (*heim_data_free_f_t)(void *); + +/* + * DB + */ + +typedef struct heim_db_data *heim_db_t; + +typedef void (*heim_db_iterator_f_t)(heim_data_t, heim_data_t, void *); + +typedef int (*heim_db_plug_open_f_t)(void *, const char *, const char *, + heim_dict_t, void **, heim_error_t *); +typedef int (*heim_db_plug_clone_f_t)(void *, void **, heim_error_t *); +typedef int (*heim_db_plug_close_f_t)(void *, heim_error_t *); +typedef int (*heim_db_plug_lock_f_t)(void *, int, heim_error_t *); +typedef int (*heim_db_plug_unlock_f_t)(void *, heim_error_t *); +typedef int (*heim_db_plug_sync_f_t)(void *, heim_error_t *); +typedef int (*heim_db_plug_begin_f_t)(void *, int, heim_error_t *); +typedef int (*heim_db_plug_commit_f_t)(void *, heim_error_t *); +typedef int (*heim_db_plug_rollback_f_t)(void *, heim_error_t *); +typedef heim_data_t (*heim_db_plug_copy_value_f_t)(void *, heim_string_t, + heim_data_t, + heim_error_t *); +typedef int (*heim_db_plug_set_value_f_t)(void *, heim_string_t, heim_data_t, + heim_data_t, heim_error_t *); +typedef int (*heim_db_plug_del_key_f_t)(void *, heim_string_t, heim_data_t, + heim_error_t *); +typedef void (*heim_db_plug_iter_f_t)(void *, heim_string_t, void *, + heim_db_iterator_f_t, heim_error_t *); + +struct heim_db_type { + int version; + heim_db_plug_open_f_t openf; + heim_db_plug_clone_f_t clonef; + heim_db_plug_close_f_t closef; + heim_db_plug_lock_f_t lockf; + heim_db_plug_unlock_f_t unlockf; + heim_db_plug_sync_f_t syncf; + heim_db_plug_begin_f_t beginf; + heim_db_plug_commit_f_t commitf; + heim_db_plug_rollback_f_t rollbackf; + heim_db_plug_copy_value_f_t copyf; + heim_db_plug_set_value_f_t setf; + heim_db_plug_del_key_f_t delf; + heim_db_plug_iter_f_t iterf; +}; + +extern struct heim_db_type heim_sorted_text_file_dbtype; + +#define HEIM_DB_TYPE_VERSION_01 1 + +/* + * Number + */ + +typedef struct heim_number_data *heim_number_t; + +/* + * Autorelease + */ + +typedef struct heim_auto_release * heim_auto_release_t; + +/* + * JSON + */ +typedef enum heim_json_flags { + HEIM_JSON_F_NO_C_NULL = 1, + HEIM_JSON_F_STRICT_STRINGS = 2, + HEIM_JSON_F_NO_DATA = 4, + HEIM_JSON_F_NO_DATA_DICT = 8, + HEIM_JSON_F_STRICT_DICT = 16, + HEIM_JSON_F_STRICT = 31, + HEIM_JSON_F_CNULL2JSNULL = 32, + HEIM_JSON_F_TRY_DECODE_DATA = 64, + HEIM_JSON_F_ONE_LINE = 128, + HEIM_JSON_F_ESCAPE_NON_ASCII = 256, + HEIM_JSON_F_NO_ESCAPE_NON_ASCII = 512, + /* The default is to indent with one tab */ + HEIM_JSON_F_INDENT2 = 1024, + HEIM_JSON_F_INDENT4 = 2048, + HEIM_JSON_F_INDENT8 = 4096, +} heim_json_flags_t; + +/* + * Debug + */ + +/* + * Binary search. + * + * Note: these are private until integrated into the heimbase object system. + */ +typedef struct bsearch_file_handle *bsearch_file_handle; +int _bsearch_text(const char *buf, size_t buf_sz, const char *key, + char **value, size_t *location, size_t *loops); +int _bsearch_file_open(const char *fname, size_t max_sz, size_t page_sz, + bsearch_file_handle *bfh, size_t *reads); +int _bsearch_file(bsearch_file_handle bfh, const char *key, char **value, + size_t *location, size_t *loops, size_t *reads); +void _bsearch_file_info(bsearch_file_handle bfh, size_t *page_sz, + size_t *max_sz, int *blockwise); +void _bsearch_file_close(bsearch_file_handle *bfh); + +/* + * Thread-specific keys + */ + +#include +#include + +/* + * Service logging facility (moved from kdc/). + */ + +#define HEIM_SVC_AUDIT_EATWHITE 0x1 +#define HEIM_SVC_AUDIT_VIS 0x2 +#define HEIM_SVC_AUDIT_VISLAST 0x4 + +typedef struct heim_svc_req_desc_common_s *heim_svc_req_desc; + +#include + +#endif /* HEIM_BASE_H */ diff --git a/third_party/heimdal/lib/base/heimbasepriv.h b/third_party/heimdal/lib/base/heimbasepriv.h new file mode 100644 index 0000000..a431da4 --- /dev/null +++ b/third_party/heimdal/lib/base/heimbasepriv.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(HEIM_BASE_MAINTAINER) && defined(ENABLE_PTHREAD_SUPPORT) +#define HEIM_WIN32_TLS +#elif defined(WIN32) +#define HEIM_WIN32_TLS +#endif + +typedef void (*heim_type_init)(void *); +typedef heim_object_t (*heim_type_copy)(void *); +typedef int (*heim_type_cmp)(void *, void *); +typedef uintptr_t (*heim_type_hash)(void *); +typedef heim_string_t (*heim_type_description)(void *); + +typedef struct heim_type_data *heim_type_t; +typedef const struct heim_type_data *heim_const_type_t; + +struct heim_type_data { + heim_tid_t tid; + const char *name; + heim_type_init init; + heim_type_dealloc dealloc; + heim_type_copy copy; + heim_type_cmp cmp; + heim_type_hash hash; + heim_type_description desc; +}; + +heim_const_type_t _heim_get_isa(heim_object_t); + +heim_type_t +_heim_create_type(const char *name, + heim_type_init init, + heim_type_dealloc dealloc, + heim_type_copy copy, + heim_type_cmp cmp, + heim_type_hash hash, + heim_type_description desc); + +heim_object_t +_heim_alloc_object(heim_const_type_t type, size_t size); + +void * +_heim_get_isaextra(heim_object_t o, size_t idx); + +heim_tid_t +_heim_type_get_tid(heim_type_t type); + +void +_heim_make_permanent(heim_object_t ptr); + +heim_data_t +_heim_db_get_value(heim_db_t, heim_string_t, heim_data_t, heim_error_t *); + + +/* tagged tid */ +extern struct heim_type_data _heim_null_object; +extern struct heim_type_data _heim_bool_object; +extern struct heim_type_data _heim_number_object; +extern struct heim_type_data _heim_string_object; diff --git a/third_party/heimdal/lib/base/json.c b/third_party/heimdal/lib/base/json.c new file mode 100644 index 0000000..ed4ea68 --- /dev/null +++ b/third_party/heimdal/lib/base/json.c @@ -0,0 +1,1471 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" +#include +#include + +#ifndef WIN32 +#include +#endif + +static heim_base_once_t heim_json_once = HEIM_BASE_ONCE_INIT; +static heim_string_t heim_tid_data_uuid_key = NULL; + +static void +json_init_once(void *arg) +{ + heim_tid_data_uuid_key = __heim_string_constant("heimdal-type-data-76d7fca2-d0da-4b20-a126-1a10f8a0eae6"); +} + +struct twojson { + void *ctx; + void (*out)(void *, const char *); + size_t indent; + heim_json_flags_t flags; + int ret; + int first; +}; + +struct heim_strbuf { + char *str; + size_t len; + size_t alloced; + int enomem; + heim_json_flags_t flags; +}; + +static int +base2json(heim_object_t, struct twojson *, int); + +static void +indent(struct twojson *j) +{ + size_t i = j->indent; + if (j->flags & HEIM_JSON_F_ONE_LINE) + return; + if (j->flags & HEIM_JSON_F_INDENT2) + while (i--) + j->out(j->ctx, " "); + else if (j->flags & HEIM_JSON_F_INDENT4) + while (i--) + j->out(j->ctx, " "); + else if (j->flags & HEIM_JSON_F_INDENT8) + while (i--) + j->out(j->ctx, " "); + else + while (i--) + j->out(j->ctx, "\t"); +} + +static void +array2json(heim_object_t value, void *ctx, int *stop) +{ + struct twojson *j = ctx; + if (j->ret) + return; + if (j->first) { + j->first = 0; + } else { + j->out(j->ctx, NULL); /* eat previous '\n' if possible */ + j->out(j->ctx, ",\n"); + } + j->ret = base2json(value, j, 0); +} + +static void +dict2json(heim_object_t key, heim_object_t value, void *ctx) +{ + struct twojson *j = ctx; + if (j->ret) + return; + if (j->first) { + j->first = 0; + } else { + j->out(j->ctx, NULL); /* eat previous '\n' if possible */ + j->out(j->ctx, ",\n"); + } + j->ret = base2json(key, j, 0); + if (j->ret) + return; + switch (heim_get_tid(value)) { + case HEIM_TID_ARRAY: + case HEIM_TID_DICT: + case HEIM_TID_DATA: + j->out(j->ctx, ":\n"); + j->indent++; + j->ret = base2json(value, j, 0); + if (j->ret) + return; + j->indent--; + break; + default: + j->out(j->ctx, ": "); + j->ret = base2json(value, j, 1); + break; + } +} + +#ifndef WIN32 +static void +init_is_utf8(void *ptr) +{ + *(int *)ptr = strcasecmp("utf-8", nl_langinfo(CODESET)) == 0; +} +#endif + +int +heim_locale_is_utf8(void) +{ +#ifdef WIN32 + return 0; /* XXX Implement */ +#else + static int locale_is_utf8 = -1; + static heim_base_once_t once = HEIM_BASE_ONCE_INIT; + + heim_base_once_f(&once, &locale_is_utf8, init_is_utf8); + return locale_is_utf8; +#endif +} + +static void +out_escaped_bmp(struct twojson *j, const unsigned char *p, int nbytes) +{ + unsigned char e[sizeof("\\u0000")]; + unsigned codepoint; + + if (nbytes == 2) + codepoint = ((p[0] & 0x1f) << 6) | (p[1] & 0x3f); + else if (nbytes == 3) + codepoint = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); + else + abort(); + e[0] = '\\'; + e[1] = 'u'; + e[2] = codepoint >> 12; + e[2] += (e[2] < 10) ? '0' : ('A' - 10); + e[3] = (codepoint >> 8) & 0x0f; + e[3] += (e[3] < 10) ? '0' : ('A' - 10); + e[4] = (codepoint >> 4) & 0x0f; + e[4] += (e[4] < 10) ? '0' : ('A' - 10); + e[5] = codepoint & 0x0f; + e[5] += (e[5] < 10) ? '0' : ('A' - 10); + e[6] = '\0'; + j->out(j->ctx, (char *)e); +} + +static int +base2json(heim_object_t obj, struct twojson *j, int skip_indent) +{ + heim_tid_t type; + int first = 0; + + if (obj == NULL) { + if (j->flags & HEIM_JSON_F_CNULL2JSNULL) { + obj = heim_null_create(); + } else if (j->flags & HEIM_JSON_F_NO_C_NULL) { + return EINVAL; + } else { + indent(j); + j->out(j->ctx, "\n"); /* This is NOT valid JSON! */ + return 0; + } + } + + type = heim_get_tid(obj); + switch (type) { + case HEIM_TID_ARRAY: + indent(j); + j->out(j->ctx, "[\n"); + j->indent++; + first = j->first; + j->first = 1; + heim_array_iterate_f(obj, j, array2json); + j->indent--; + if (!j->first) + j->out(j->ctx, "\n"); + indent(j); + j->out(j->ctx, "]\n"); + j->first = first; + break; + + case HEIM_TID_DICT: + indent(j); + j->out(j->ctx, "{\n"); + j->indent++; + first = j->first; + j->first = 1; + heim_dict_iterate_f(obj, j, dict2json); + j->indent--; + if (!j->first) + j->out(j->ctx, "\n"); + indent(j); + j->out(j->ctx, "}\n"); + j->first = first; + break; + + case HEIM_TID_STRING: { + const unsigned char *s = (const unsigned char *)heim_string_get_utf8(obj); + const unsigned char *p; + unsigned int c, cp, ctop, cbot; + char e[sizeof("\\u0123\\u3210")]; + int good; + size_t i; + + if (!skip_indent) + indent(j); + j->out(j->ctx, "\""); + for (p = s; (c = *p); p++) { + switch (c) { + /* ASCII control characters w/ C-like escapes */ + case '\b': j->out(j->ctx, "\\b"); continue; + case '\f': j->out(j->ctx, "\\f"); continue; + case '\n': j->out(j->ctx, "\\n"); continue; + case '\r': j->out(j->ctx, "\\r"); continue; + case '\t': j->out(j->ctx, "\\t"); continue; + /* Other must-escape non-control ASCII characters */ + case '"': j->out(j->ctx, "\\\""); continue; + case '\\': j->out(j->ctx, "\\\\"); continue; + default: break; + } + + /* + * JSON string encoding is... complex. + * + * Invalid UTF-8 w/ HEIM_JSON_F_STRICT_STRINGS set -> return 1 + * + * Invalid UTF-8 w/o HEIM_JSON_F_STRICT_STRINGS set -> pass + * through, a sort of Heimdal WTF-8, but not _the_ WTF-8. + */ + if (c < 0x20) { + /* ASCII control character w/o C-like escape */ + e[0] = '\\'; + e[1] = 'u'; + e[2] = '0'; + e[3] = '0'; + e[4] = "0123456789ABCDEF"[c>>4]; + e[5] = "0123456789ABCDEF"[c & 0x0f]; + e[6] = '\0'; + j->out(j->ctx, e); + continue; + } + if (c < 0x80) { + /* ASCII */ + e[0] = c; + e[1] = '\0'; + j->out(j->ctx, e); + continue; + } + if ((c & 0xc0) == 0x80) { + /* UTF-8 bare non-leading byte */ + if (!(j->flags & HEIM_JSON_F_STRICT_STRINGS)) { + e[0] = c; + e[1] = '\0'; + j->out(j->ctx, e); + continue; + } + return 1; + } + if ((c & 0xe0) == 0xc0) { + /* UTF-8 leading byte of two-byte sequence */ + good = 1; + for (i = 1; i < 2 && good && p[i]; i++) { + if ((p[i] & 0xc0) != 0x80) + good = 0; + } + if (i != 2) + good = 0; + if (!good && !(j->flags & HEIM_JSON_F_STRICT_STRINGS)) { + e[0] = c; + e[1] = '\0'; + j->out(j->ctx, e); + continue; + } else if (!good) { + return 1; + } + if (j->flags & HEIM_JSON_F_ESCAPE_NON_ASCII) { + out_escaped_bmp(j, p, 2); + p += 1; + continue; + } + e[0] = c; + e[1] = p[1]; + e[2] = '\0'; + j->out(j->ctx, e); + p += 1; + continue; + } + if ((c & 0xf0) == 0xe0) { + /* UTF-8 leading byte of three-byte sequence */ + good = 1; + for (i = 1; i < 3 && good && p[i]; i++) { + if ((p[i] & 0xc0) != 0x80) + good = 0; + } + if (i != 3) + good = 0; + if (!good && !(j->flags & HEIM_JSON_F_STRICT_STRINGS)) { + e[0] = c; + e[1] = '\0'; + j->out(j->ctx, e); + continue; + } else if (!good) { + return 1; + } + if (j->flags & HEIM_JSON_F_ESCAPE_NON_ASCII) { + out_escaped_bmp(j, p, 3); + p += 2; + continue; + } + e[0] = c; + e[1] = p[1]; + e[2] = p[2]; + e[3] = '\0'; + j->out(j->ctx, e); + p += 2; + continue; + } + + if (c > 0xf7) { + /* Invalid UTF-8 leading byte */ + if (!(j->flags & HEIM_JSON_F_STRICT_STRINGS)) { + e[0] = c; + e[1] = '\0'; + j->out(j->ctx, e); + continue; + } + return 1; + } + + /* + * A codepoint > U+FFFF, needs encoding a la UTF-16 surrogate + * pair because JSON takes after JS which uses UTF-16. Ugly. + */ + cp = c & 0x7; + good = 1; + for (i = 1; i < 4 && good && p[i]; i++) { + if ((p[i] & 0xc0) == 0x80) + cp = (cp << 6) | (p[i] & 0x3f); + else + good = 0; + } + if (i != 4) + good = 0; + if (!good && !(j->flags & HEIM_JSON_F_STRICT_STRINGS)) { + e[0] = c; + e[1] = '\0'; + j->out(j->ctx, e); + continue; + } else if (!good) { + return 1; + } + p += 3; + + cp -= 0x10000; + ctop = 0xD800 + (cp >> 10); + cbot = 0xDC00 + (cp & 0x3ff); + + e[0 ] = '\\'; + e[1 ] = 'u'; + e[2 ] = "0123456789ABCDEF"[(ctop ) >> 12]; + e[3 ] = "0123456789ABCDEF"[(ctop & 0x0f00) >> 8]; + e[4 ] = "0123456789ABCDEF"[(ctop & 0x00f0) >> 4]; + e[5 ] = "0123456789ABCDEF"[(ctop & 0x000f) ]; + e[6 ] = '\\'; + e[7 ] = 'u'; + e[8 ] = "0123456789ABCDEF"[(cbot ) >> 12]; + e[9 ] = "0123456789ABCDEF"[(cbot & 0x0f00) >> 8]; + e[10] = "0123456789ABCDEF"[(cbot & 0x00f0) >> 4]; + e[11] = "0123456789ABCDEF"[(cbot & 0x000f) ]; + e[12] = '\0'; + j->out(j->ctx, e); + continue; + } + j->out(j->ctx, "\""); + break; + } + + case HEIM_TID_DATA: { + heim_dict_t d; + heim_string_t v; + const heim_octet_string *data; + char *b64 = NULL; + int ret; + + if (j->flags & HEIM_JSON_F_NO_DATA) + return EINVAL; /* JSON doesn't do binary */ + + data = heim_data_get_data(obj); + ret = rk_base64_encode(data->data, data->length, &b64); + if (ret < 0 || b64 == NULL) + return ENOMEM; + + if (j->flags & HEIM_JSON_F_NO_DATA_DICT) { + indent(j); + j->out(j->ctx, "\""); + j->out(j->ctx, b64); /* base64-encode; hope there's no aliasing */ + j->out(j->ctx, "\""); + free(b64); + } else { + /* + * JSON has no way to represent binary data, therefore the + * following is a Heimdal-specific convention. + * + * We encode binary data as a dict with a single very magic + * key with a base64-encoded value. The magic key includes + * a uuid, so we're not likely to alias accidentally. + */ + d = heim_dict_create(2); + if (d == NULL) { + free(b64); + return ENOMEM; + } + v = heim_string_ref_create(b64, free); + if (v == NULL) { + free(b64); + heim_release(d); + return ENOMEM; + } + ret = heim_dict_set_value(d, heim_tid_data_uuid_key, v); + heim_release(v); + if (ret) { + heim_release(d); + return ENOMEM; + } + ret = base2json(d, j, 0); + heim_release(d); + if (ret) + return ret; + } + break; + } + + case HEIM_TID_NUMBER: { + char num[32]; + if (!skip_indent) + indent(j); + snprintf(num, sizeof (num), "%d", heim_number_get_int(obj)); + j->out(j->ctx, num); + break; + } + case HEIM_TID_NULL: + if (!skip_indent) + indent(j); + j->out(j->ctx, "null"); + break; + case HEIM_TID_BOOL: + if (!skip_indent) + indent(j); + j->out(j->ctx, heim_bool_val(obj) ? "true" : "false"); + break; + default: + return 1; + } + return 0; +} + +static int +heim_base2json(heim_object_t obj, void *ctx, heim_json_flags_t flags, + void (*out)(void *, const char *)) +{ + struct twojson j; + + heim_base_once_f(&heim_json_once, NULL, json_init_once); + + j.indent = 0; + j.ctx = ctx; + j.out = out; + j.flags = flags; + j.ret = 0; + j.first = 1; + + if (!(flags & HEIM_JSON_F_NO_ESCAPE_NON_ASCII) && + !heim_locale_is_utf8()) + j.flags |= HEIM_JSON_F_ESCAPE_NON_ASCII; + + return base2json(obj, &j, 0); +} + + +/* + * + */ + +struct parse_ctx { + unsigned long lineno; + const uint8_t *p; + const uint8_t *pstart; + const uint8_t *pend; + heim_error_t error; + size_t depth; + heim_json_flags_t flags; +}; + + +static heim_object_t +parse_value(struct parse_ctx *ctx); + +/* + * This function eats whitespace, but, critically, it also succeeds + * only if there's anything left to parse. + */ +static int +white_spaces(struct parse_ctx *ctx) +{ + while (ctx->p < ctx->pend) { + uint8_t c = *ctx->p; + if (c == ' ' || c == '\t' || c == '\r') { + + } else if (c == '\n') { + ctx->lineno++; + } else + return 0; + (ctx->p)++; + } + return -1; +} + +static int +is_number(uint8_t n) +{ + return ('0' <= n && n <= '9'); +} + +static heim_number_t +parse_number(struct parse_ctx *ctx) +{ + int number = 0, neg = 1; + + if (ctx->p >= ctx->pend) + return NULL; + + if (*ctx->p == '-') { + if (ctx->p + 1 >= ctx->pend) + return NULL; + neg = -1; + ctx->p += 1; + } + + while (ctx->p < ctx->pend) { + if (is_number(*ctx->p)) { + number = (number * 10) + (*ctx->p - '0'); + } else { + break; + } + ctx->p += 1; + } + + return heim_number_create(number * neg); +} + +/* + * Read 4 hex digits from ctx->p. + * + * If we don't have enough, rewind ctx->p and return -1 . + */ +static int +unescape_unicode(struct parse_ctx *ctx) +{ + int c = 0; + int i; + + for (i = 0; i < 4 && ctx->p < ctx->pend; i++, ctx->p++) { + if (*ctx->p >= '0' && *ctx->p <= '9') { + c = (c << 4) + (*ctx->p - '0'); + } else if (*ctx->p >= 'A' && *ctx->p <= 'F') { + c = (c << 4) + (10 + *ctx->p - 'A'); + } else if (*ctx->p >= 'a' && *ctx->p <= 'f') { + c = (c << 4) + (10 + *ctx->p - 'a'); + } else { + ctx->p -= i; + return -1; + } + } + return c; +} + +static int +encode_utf8(struct parse_ctx *ctx, char **pp, char *pend, int c) +{ + char *p = *pp; + + if (c < 0x80) { + /* ASCII */ + if (p >= pend) return 0; + *(p++) = c; + *pp = p; + return 1; + } + if (c < 0x800) { + /* 2 code unit UTF-8 sequence */ + if (p >= pend) return 0; + *(p++) = 0xc0 | ((c >> 6) ); + if (p == pend) return 0; + *(p++) = 0x80 | ((c ) & 0x3f); + *pp = p; + return 1; + } + if (c < 0x10000) { + /* 3 code unit UTF-8 sequence */ + if (p >= pend) return 0; + *(p++) = 0xe0 | ((c >> 12) ); + if (p == pend) return 0; + *(p++) = 0x80 | ((c >> 6) & 0x3f); + if (p == pend) return 0; + *(p++) = 0x80 | ((c) & 0x3f); + *pp = p; + return 1; + } + if (c < 0x110000) { + /* 4 code unit UTF-8 sequence */ + if (p >= pend) return 0; + *(p++) = 0xf0 | ((c >> 18) ); + if (p == pend) return 0; + *(p++) = 0x80 | ((c >> 12) & 0x3f); + if (p == pend) return 0; + *(p++) = 0x80 | ((c >> 6) & 0x3f); + if (p == pend) return 0; + *(p++) = 0x80 | ((c) & 0x3f); + *pp = p; + return 1; + } + return 0; +} + +static heim_string_t +parse_string_error(struct parse_ctx *ctx, + char *freeme, + const char *msg) +{ + free(freeme); + ctx->error = heim_error_create(EINVAL, "%s at %lu", msg, ctx->lineno); + return NULL; +} + +static heim_string_t +parse_string(struct parse_ctx *ctx) +{ + const uint8_t *start; + heim_object_t o; + size_t alloc_len = 0; + size_t need = 0; + char *p0, *p, *pend; + int strict = ctx->flags & HEIM_JSON_F_STRICT_STRINGS; + int binary = 0; + + if (*ctx->p != '"') + return parse_string_error(ctx, NULL, + "Expected a JSON string but found " + "something else"); + start = ++(ctx->p); + + /* Estimate how many bytes we need to allocate */ + p0 = p = pend = NULL; + for (need = 1; ctx->p < ctx->pend; ctx->p++) { + need++; + if (*ctx->p == '\\') + ctx->p++; + else if (*ctx->p == '"') + break; + } + if (ctx->p == ctx->pend) + return parse_string_error(ctx, NULL, "Unterminated JSON string"); + + ctx->p = start; + while (ctx->p < ctx->pend) { + const unsigned char *p_save; + int32_t ctop, cbot; + + if (*ctx->p == '"') { + ctx->p++; + break; + } + + /* Allocate or resize our output buffer if need be */ + if (need || p == pend) { + char *tmp; + + /* + * Work out how far p is into p0 to re-esablish p after + * the realloc() + */ + size_t p0_to_p_len = (p - p0); + + tmp = realloc(p0, alloc_len + need + 5 /* slop? */); + + if (tmp == NULL) { + ctx->error = heim_error_create_enomem(); + free(p0); + return NULL; + } + alloc_len += need + 5; + + /* + * We have two pointers, p and p0, we want to keep them + * pointing into the same memory after the realloc() + */ + p = tmp + p0_to_p_len; + p0 = tmp; + pend = p0 + alloc_len; + + need = 0; + } + + if (*ctx->p != '\\') { + unsigned char c = *ctx->p; + + /* + * Not backslashed -> consume now. + * + * NOTE: All cases in this block must continue or return w/ error. + */ + + /* Check for unescaped ASCII control characters */ + if (c == '\n') { + if (strict) + return parse_string_error(ctx, p0, + "Unescaped newline in JSON string"); + /* Count the newline but don't add it to the decoding */ + ctx->lineno++; + } else if (strict && *ctx->p <= 0x1f) { + return parse_string_error(ctx, p0, "Unescaped ASCII control character"); + } else if (c == 0) { + binary = 1; + } + if (!strict || c < 0x80) { + /* ASCII, or not strict -> no need to validate */ + *(p++) = c; + ctx->p++; + continue; + } + + /* + * Being strict for parsing means we want to detect malformed UTF-8 + * sequences. + * + * If not strict then we just go on below and add to `p' whatever + * bytes we find in `ctx->p' as we find them. + * + * For each two-byte sequence we need one more byte in `p[]'. For + * each three-byte sequence we need two more bytes in `p[]'. + * + * Setting `need' and looping will cause `p0' to be grown. + * + * NOTE: All cases in this block must continue or return w/ error. + */ + if ((c & 0xe0) == 0xc0) { + /* Two-byte UTF-8 encoding */ + if (pend - p < 2) { + need = 2; + continue; /* realloc p0 */ + } + + *(p++) = c; + ctx->p++; + if (ctx->p == ctx->pend) + return parse_string_error(ctx, p0, "Truncated UTF-8"); + c = *(ctx->p++); + if ((c & 0xc0) != 0x80) + return parse_string_error(ctx, p0, "Truncated UTF-8"); + *(p++) = c; + continue; + } + if ((c & 0xf0) == 0xe0) { + /* Three-byte UTF-8 encoding */ + if (pend - p < 3) { + need = 3; + continue; /* realloc p0 */ + } + + *(p++) = c; + ctx->p++; + if (ctx->p == ctx->pend) + return parse_string_error(ctx, p0, "Truncated UTF-8"); + c = *(ctx->p++); + if ((c & 0xc0) != 0x80) + return parse_string_error(ctx, p0, "Truncated UTF-8"); + *(p++) = c; + c = *(ctx->p++); + if ((c & 0xc0) != 0x80) + return parse_string_error(ctx, p0, "Truncated UTF-8"); + *(p++) = c; + continue; + } + if ((c & 0xf8) == 0xf0) + return parse_string_error(ctx, p0, "UTF-8 sequence not " + "encoded as escaped UTF-16"); + if ((c & 0xc0) == 0x80) + return parse_string_error(ctx, p0, + "Invalid UTF-8 " + "(bare continuation code unit)"); + + return parse_string_error(ctx, p0, "Not UTF-8"); + } + + /* Backslash-quoted character */ + ctx->p++; + if (ctx->p == ctx->pend) { + ctx->error = + heim_error_create(EINVAL, + "Unterminated JSON string at line %lu", + ctx->lineno); + free(p0); + return NULL; + } + switch (*ctx->p) { + /* Simple escapes */ + case 'b': *(p++) = '\b'; ctx->p++; continue; + case 'f': *(p++) = '\f'; ctx->p++; continue; + case 'n': *(p++) = '\n'; ctx->p++; continue; + case 'r': *(p++) = '\r'; ctx->p++; continue; + case 't': *(p++) = '\t'; ctx->p++; continue; + case '"': *(p++) = '"'; ctx->p++; continue; + case '\\': *(p++) = '\\'; ctx->p++; continue; + /* Escaped Unicode handled below */ + case 'u': + /* + * Worst case for !strict we need 11 bytes for a truncated non-BMP + * codepoint escape. Call it 12. + */ + if (strict) + need = 4; + else + need = 12; + if (pend - p < need) { + /* Go back to the backslash, realloc, try again */ + ctx->p--; + continue; + } + + need = 0; + ctx->p++; + break; + default: + if (!strict) { + *(p++) = *ctx->p; + ctx->p++; + continue; + } + ctx->error = + heim_error_create(EINVAL, + "Invalid backslash escape at line %lu", + ctx->lineno); + free(p0); + return NULL; + } + + /* Unicode code point */ + if (pend - p < 12) { + need = 12; + ctx->p -= 2; /* for "\\u" */ + continue; /* This will cause p0 to be realloc'ed */ + } + p_save = ctx->p; + cbot = -3; + ctop = unescape_unicode(ctx); + if (ctop == -1 && strict) + return parse_string_error(ctx, p0, "Invalid escaped Unicode"); + if (ctop == -1) { + /* + * Not strict; tolerate bad input. + * + * Output "\\u" and then loop to treat what we expected to be four + * digits as if they were not part of an escaped Unicode codepoint. + */ + ctx->p = p_save; + if (p < pend) + *(p++) = '\\'; + if (p < pend) + *(p++) = 'u'; + continue; + } + if (ctop == 0) { + *(p++) = '\0'; + binary = 1; + continue; + } + if (ctop < 0xd800) { + if (!encode_utf8(ctx, &p, pend, ctop)) + return parse_string_error(ctx, p0, + "Internal JSON string parse error"); + continue; + } + + /* + * We parsed the top escaped codepoint of a surrogate pair encoding + * of a non-BMP Unicode codepoint. What follows must be another + * escaped codepoint. + */ + if (ctx->p < ctx->pend && ctx->p[0] == '\\') + ctx->p++; + else + ctop = -1; + if (ctop > -1 && ctx->p < ctx->pend && ctx->p[0] == 'u') + ctx->p++; + else + ctop = -1; + if (ctop > -1) { + /* Parse the hex digits of the bottom half of the surrogate pair */ + cbot = unescape_unicode(ctx); + if (cbot == -1 || cbot < 0xdc00) + ctop = -1; + } + if (ctop == -1) { + if (strict) + return parse_string_error(ctx, p0, + "Invalid surrogate pair"); + + /* + * Output "\\u", rewind, output the digits of `ctop'. + * + * When we get to what should have been the bottom half of the + * pair we'll necessarily fail to parse it as a normal escaped + * Unicode codepoint, and once again, rewind and output its digits. + */ + if (p < pend) + *(p++) = '\\'; + if (p < pend) + *(p++) = 'u'; + ctx->p = p_save; + continue; + } + + /* Finally decode the surrogate pair then encode as UTF-8 */ + ctop -= 0xd800; + cbot -= 0xdc00; + if (!encode_utf8(ctx, &p, pend, 0x10000 + ((ctop << 10) | (cbot & 0x3ff)))) + return parse_string_error(ctx, p0, + "Internal JSON string parse error"); + } + + if (p0 == NULL) + return heim_string_create(""); + + /* NUL-terminate for rk_base64_decode() and plain paranoia */ + if (p0 != NULL && p == pend) { + /* + * Work out how far p is into p0 to re-establish p after + * the realloc() + */ + size_t p0_to_pend_len = (pend - p0); + char *tmp = realloc(p0, 1 + p0_to_pend_len); + + if (tmp == NULL) { + ctx->error = heim_error_create_enomem(); + free(p0); + return NULL; + } + /* + * We have three pointers, p, pend (which are the same) + * and p0, we want to keep them pointing into the same + * memory after the realloc() + */ + p = tmp + p0_to_pend_len; + + pend = p + 1; + p0 = tmp; + } + *(p++) = '\0'; + + /* If there's embedded NULs, it's not a C string */ + if (binary) { + o = heim_data_ref_create(p0, (p - 1) - p0, free); + return o; + } + + /* Sadly this will copy `p0' */ + o = heim_string_create_with_bytes(p0, p - p0); + free(p0); + return o; +} + +static int +parse_pair(heim_dict_t dict, struct parse_ctx *ctx) +{ + heim_string_t key; + heim_object_t value; + + if (white_spaces(ctx)) + return -1; + + if (*ctx->p == '}') { + ctx->p++; + return 0; + } + + if (ctx->flags & HEIM_JSON_F_STRICT_DICT) + /* JSON allows only string keys */ + key = parse_string(ctx); + else + /* heim_dict_t allows any heim_object_t as key */ + key = parse_value(ctx); + if (key == NULL) + /* Even heim_dict_t does not allow C NULLs as keys though! */ + return -1; + + if (white_spaces(ctx)) { + heim_release(key); + return -1; + } + + if (*ctx->p != ':') { + heim_release(key); + return -1; + } + + ctx->p += 1; /* safe because we call white_spaces() next */ + + if (white_spaces(ctx)) { + heim_release(key); + return -1; + } + + value = parse_value(ctx); + if (value == NULL && + (ctx->error != NULL || (ctx->flags & HEIM_JSON_F_NO_C_NULL))) { + if (ctx->error == NULL) + ctx->error = heim_error_create(EINVAL, "Invalid JSON encoding"); + heim_release(key); + return -1; + } + heim_dict_set_value(dict, key, value); + heim_release(key); + heim_release(value); + + if (white_spaces(ctx)) + return -1; + + if (*ctx->p == '}') { + /* + * Return 1 but don't consume the '}' so we can count the one + * pair in a one-pair dict + */ + return 1; + } else if (*ctx->p == ',') { + ctx->p++; + return 1; + } + return -1; +} + +static heim_dict_t +parse_dict(struct parse_ctx *ctx) +{ + heim_dict_t dict; + size_t count = 0; + int ret; + + heim_assert(*ctx->p == '{', "string doesn't start with {"); + + dict = heim_dict_create(11); + if (dict == NULL) { + ctx->error = heim_error_create_enomem(); + return NULL; + } + + ctx->p += 1; /* safe because parse_pair() calls white_spaces() first */ + + while ((ret = parse_pair(dict, ctx)) > 0) + count++; + if (ret < 0) { + heim_release(dict); + return NULL; + } + if (count == 1 && !(ctx->flags & HEIM_JSON_F_NO_DATA_DICT)) { + heim_object_t v = heim_dict_copy_value(dict, heim_tid_data_uuid_key); + + /* + * Binary data encoded as a dict with a single magic key with + * base64-encoded value? Decode as heim_data_t. + */ + if (v != NULL && heim_get_tid(v) == HEIM_TID_STRING) { + void *buf; + size_t len; + + buf = malloc(strlen(heim_string_get_utf8(v))); + if (buf == NULL) { + heim_release(dict); + heim_release(v); + ctx->error = heim_error_create_enomem(); + return NULL; + } + len = rk_base64_decode(heim_string_get_utf8(v), buf); + heim_release(v); + if (len == -1) { + free(buf); + return dict; /* assume aliasing accident */ + } + heim_release(dict); + return (heim_dict_t)heim_data_ref_create(buf, len, free); + } + } + return dict; +} + +static int +parse_item(heim_array_t array, struct parse_ctx *ctx) +{ + heim_object_t value; + + if (white_spaces(ctx)) + return -1; + + if (*ctx->p == ']') { + ctx->p++; /* safe because parse_value() calls white_spaces() first */ + return 0; + } + + value = parse_value(ctx); + if (value == NULL && + (ctx->error || (ctx->flags & HEIM_JSON_F_NO_C_NULL))) + return -1; + + heim_array_append_value(array, value); + heim_release(value); + + if (white_spaces(ctx)) + return -1; + + if (*ctx->p == ']') { + ctx->p++; + return 0; + } else if (*ctx->p == ',') { + ctx->p++; + return 1; + } + return -1; +} + +static heim_array_t +parse_array(struct parse_ctx *ctx) +{ + heim_array_t array = heim_array_create(); + int ret; + + heim_assert(*ctx->p == '[', "array doesn't start with ["); + ctx->p += 1; + + while ((ret = parse_item(array, ctx)) > 0) + ; + if (ret < 0) { + heim_release(array); + return NULL; + } + return array; +} + +static heim_object_t +parse_value(struct parse_ctx *ctx) +{ + size_t len; + heim_object_t o; + + if (white_spaces(ctx)) + return NULL; + + if (*ctx->p == '"') { + return parse_string(ctx); + } else if (*ctx->p == '{') { + if (ctx->depth-- == 1) { + ctx->error = heim_error_create(EINVAL, "JSON object too deep"); + return NULL; + } + o = parse_dict(ctx); + ctx->depth++; + return o; + } else if (*ctx->p == '[') { + if (ctx->depth-- == 1) { + ctx->error = heim_error_create(EINVAL, "JSON object too deep"); + return NULL; + } + o = parse_array(ctx); + ctx->depth++; + return o; + } else if (is_number(*ctx->p) || *ctx->p == '-') { + return parse_number(ctx); + } + + len = ctx->pend - ctx->p; + + if ((ctx->flags & HEIM_JSON_F_NO_C_NULL) == 0 && + len >= 6 && memcmp(ctx->p, "", 6) == 0) { + ctx->p += 6; + return heim_null_create(); + } else if (len >= 4 && memcmp(ctx->p, "null", 4) == 0) { + ctx->p += 4; + return heim_null_create(); + } else if (len >= 4 && strncasecmp((char *)ctx->p, "true", 4) == 0) { + ctx->p += 4; + return heim_bool_create(1); + } else if (len >= 5 && strncasecmp((char *)ctx->p, "false", 5) == 0) { + ctx->p += 5; + return heim_bool_create(0); + } + + ctx->error = heim_error_create(EINVAL, "unknown char %c at %lu line %lu", + (char)*ctx->p, + (unsigned long)(ctx->p - ctx->pstart), + ctx->lineno); + return NULL; +} + + +heim_object_t +heim_json_create(const char *string, size_t max_depth, heim_json_flags_t flags, + heim_error_t *error) +{ + return heim_json_create_with_bytes(string, strlen(string), max_depth, flags, + error); +} + +heim_object_t +heim_json_create_with_bytes(const void *data, size_t length, size_t max_depth, + heim_json_flags_t flags, heim_error_t *error) +{ + struct parse_ctx ctx; + heim_object_t o; + + heim_base_once_f(&heim_json_once, NULL, json_init_once); + + ctx.lineno = 1; + ctx.p = data; + ctx.pstart = data; + ctx.pend = ((uint8_t *)data) + length; + ctx.error = NULL; + ctx.flags = flags; + ctx.depth = max_depth; + + o = parse_value(&ctx); + + if (o == NULL && error) { + *error = ctx.error; + } else if (ctx.error) { + heim_release(ctx.error); + } + + return o; +} + + +static void +show_printf(void *ctx, const char *str) +{ + if (str == NULL) + return; + fprintf(ctx, "%s", str); +} + +/** + * Dump a heimbase object to stderr (useful from the debugger!) + * + * @param obj object to dump using JSON or JSON-like format + * + * @addtogroup heimbase + */ +void +heim_show(heim_object_t obj) +{ + heim_base2json(obj, stderr, HEIM_JSON_F_NO_DATA_DICT, show_printf); +} + +static void +strbuf_add(void *ctx, const char *str) +{ + struct heim_strbuf *strbuf = ctx; + size_t len; + + if (strbuf->enomem) + return; + + if (str == NULL) { + /* + * Eat the last '\n'; this is used when formatting dict pairs + * and array items so that the ',' separating them is never + * preceded by a '\n'. + */ + if (strbuf->len > 0 && strbuf->str[strbuf->len - 1] == '\n') + strbuf->len--; + return; + } + + len = strlen(str); + if ((len + 1) > (strbuf->alloced - strbuf->len)) { + size_t new_len = strbuf->alloced + (strbuf->alloced >> 2) + len + 1; + char *s; + + s = realloc(strbuf->str, new_len); + if (s == NULL) { + strbuf->enomem = 1; + return; + } + strbuf->str = s; + strbuf->alloced = new_len; + } + /* +1 so we copy the NUL */ + (void) memcpy(strbuf->str + strbuf->len, str, len + 1); + strbuf->len += len; + if (strbuf->str[strbuf->len - 1] == '\n' && + strbuf->flags & HEIM_JSON_F_ONE_LINE) + strbuf->len--; +} + +#define STRBUF_INIT_SZ 64 + +heim_string_t +heim_json_copy_serialize(heim_object_t obj, heim_json_flags_t flags, heim_error_t *error) +{ + heim_string_t str; + struct heim_strbuf strbuf; + int ret; + + if (error) + *error = NULL; + + memset(&strbuf, 0, sizeof (strbuf)); + strbuf.str = malloc(STRBUF_INIT_SZ); + if (strbuf.str == NULL) { + if (error) + *error = heim_error_create_enomem(); + return NULL; + } + strbuf.len = 0; + strbuf.alloced = STRBUF_INIT_SZ; + strbuf.str[0] = '\0'; + strbuf.flags = flags; + + ret = heim_base2json(obj, &strbuf, flags, strbuf_add); + if (ret || strbuf.enomem) { + if (error) { + if (strbuf.enomem || ret == ENOMEM) + *error = heim_error_create_enomem(); + else + *error = heim_error_create(1, "Impossible to JSON-encode " + "object"); + } + free(strbuf.str); + return NULL; + } + if (flags & HEIM_JSON_F_ONE_LINE) { + strbuf.flags &= ~HEIM_JSON_F_ONE_LINE; + strbuf_add(&strbuf, "\n"); + } + str = heim_string_ref_create(strbuf.str, free); + if (str == NULL) { + if (error) + *error = heim_error_create_enomem(); + free(strbuf.str); + } + return str; +} + +struct heim_eq_f_ctx { + heim_dict_t other; + int ret; +}; + +static void +heim_eq_dict_iter_f(heim_object_t key, heim_object_t val, void *d) +{ + struct heim_eq_f_ctx *ctx = d; + heim_object_t other_val; + + if (!ctx->ret) + return; + + /* + * This doesn't work if the key is an array or a dict, which, anyways, + * isn't allowed in JSON, though we allow it. + */ + other_val = heim_dict_get_value(ctx->other, key); + ctx->ret = heim_json_eq(val, other_val); +} + +int +heim_json_eq(heim_object_t a, heim_object_t b) +{ + heim_tid_t atid, btid; + + if (a == b) + return 1; + if (a == NULL || b == NULL) + return 0; + atid = heim_get_tid(a); + btid = heim_get_tid(b); + if (atid != btid) + return 0; + switch (atid) { + case HEIM_TID_ARRAY: { + size_t len = heim_array_get_length(b); + size_t i; + + if (heim_array_get_length(a) != len) + return 0; + for (i = 0; i < len; i++) { + if (!heim_json_eq(heim_array_get_value(a, i), + heim_array_get_value(b, i))) + return 0; + } + return 1; + } + case HEIM_TID_DICT: { + struct heim_eq_f_ctx ctx; + + ctx.other = b; + ctx.ret = 1; + heim_dict_iterate_f(a, &ctx, heim_eq_dict_iter_f); + + if (ctx.ret) { + ctx.other = a; + heim_dict_iterate_f(b, &ctx, heim_eq_dict_iter_f); + } + return ctx.ret; + } + case HEIM_TID_STRING: + return strcmp(heim_string_get_utf8(a), heim_string_get_utf8(b)) == 0; + case HEIM_TID_DATA: { + return heim_data_get_length(a) == heim_data_get_length(b) && + memcmp(heim_data_get_ptr(a), heim_data_get_ptr(b), + heim_data_get_length(a)) == 0; + } + case HEIM_TID_NUMBER: + return heim_number_get_long(a) == heim_number_get_long(b); + case HEIM_TID_NULL: + case HEIM_TID_BOOL: + return heim_bool_val(a) == heim_bool_val(b); + default: + break; + } + return 0; +} diff --git a/third_party/heimdal/lib/base/log.c b/third_party/heimdal/lib/base/log.c new file mode 100644 index 0000000..9a97276 --- /dev/null +++ b/third_party/heimdal/lib/base/log.c @@ -0,0 +1,1079 @@ +/* + * Copyright (c) 1997-2020 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" +#include "heim_threads.h" +#include "heimbase-atomics.h" +#include "heimbase.h" +#include "heimbase-svc.h" +#include +#include +#include +#include + +struct heim_log_facility_internal { + int min; + int max; + heim_log_log_func_t log_func; + heim_log_close_func_t close_func; + void *data; +}; + +struct heim_log_facility_s { + char *program; + heim_base_atomic(uint32_t) refs; + size_t len; + struct heim_log_facility_internal *val; +}; + +typedef struct heim_pcontext_s *heim_pcontext; +typedef struct heim_pconfig *heim_pconfig; +struct heim_svc_req_desc_common_s { + HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS; +}; + +static struct heim_log_facility_internal * +log_realloc(heim_log_facility *f) +{ + struct heim_log_facility_internal *fp; + fp = realloc(f->val, (f->len + 1) * sizeof(*f->val)); + if (fp == NULL) + return NULL; + f->len++; + f->val = fp; + fp += f->len - 1; + return fp; +} + +struct s2i { + const char *s; + int val; +}; + +#define L(X) { #X, LOG_ ## X } + +static struct s2i syslogvals[] = { + L(EMERG), + L(ALERT), + L(CRIT), + L(ERR), + L(WARNING), + L(NOTICE), + L(INFO), + L(DEBUG), + + L(AUTH), +#ifdef LOG_AUTHPRIV + L(AUTHPRIV), +#endif +#ifdef LOG_CRON + L(CRON), +#endif + L(DAEMON), +#ifdef LOG_FTP + L(FTP), +#endif + L(KERN), + L(LPR), + L(MAIL), +#ifdef LOG_NEWS + L(NEWS), +#endif + L(SYSLOG), + L(USER), +#ifdef LOG_UUCP + L(UUCP), +#endif + L(LOCAL0), + L(LOCAL1), + L(LOCAL2), + L(LOCAL3), + L(LOCAL4), + L(LOCAL5), + L(LOCAL6), + L(LOCAL7), + { NULL, -1 } +}; + +static int +find_value(const char *s, struct s2i *table) +{ + while (table->s && strcasecmp(table->s, s) != 0) + table++; + return table->val; +} + +heim_error_code +heim_initlog(heim_context context, + const char *program, + heim_log_facility **fac) +{ + heim_log_facility *f = calloc(1, sizeof(*f)); + if (f == NULL) + return heim_enomem(context); + f->refs = 1; + f->program = strdup(program); + if (f->program == NULL) { + free(f); + return heim_enomem(context); + } + *fac = f; + return 0; +} + +heim_log_facility * +heim_log_ref(heim_log_facility *fac) +{ + if (fac) + (void) heim_base_atomic_inc_32(&fac->refs); + return fac; +} + +heim_error_code +heim_addlog_func(heim_context context, + heim_log_facility *fac, + int min, + int max, + heim_log_log_func_t log_func, + heim_log_close_func_t close_func, + void *data) +{ + struct heim_log_facility_internal *fp = log_realloc(fac); + if (fp == NULL) + return heim_enomem(context); + fp->min = min; + fp->max = max; + fp->log_func = log_func; + fp->close_func = close_func; + fp->data = data; + return 0; +} + + +struct _heimdal_syslog_data{ + int priority; +}; + +static void HEIM_CALLCONV +log_syslog(heim_context context, const char *timestr, + const char *msg, void *data) +{ + struct _heimdal_syslog_data *s = data; + syslog(s->priority, "%s", msg); +} + +static void HEIM_CALLCONV +close_syslog(void *data) +{ + free(data); + closelog(); +} + +static heim_error_code +open_syslog(heim_context context, + heim_log_facility *facility, int min, int max, + const char *sev, const char *fac) +{ + struct _heimdal_syslog_data *sd; + heim_error_code ret; + int i; + + if (facility == NULL) + return EINVAL; + if ((sd = calloc(1, sizeof(*sd))) == NULL) + return heim_enomem(context); + i = find_value(sev, syslogvals); + if (i == -1) + i = LOG_ERR; + sd->priority = i; + i = find_value(fac, syslogvals); + if (i == -1) + i = LOG_AUTH; + sd->priority |= i; + roken_openlog(facility->program, LOG_PID | LOG_NDELAY, i); + ret = heim_addlog_func(context, facility, min, max, log_syslog, + close_syslog, sd); + if (ret) + free(sd); + else + sd = NULL; + return ret; +} + +struct file_data { + char *filename; + const char *mode; + struct timeval tv; + FILE *fd; + int disp; +#define FILEDISP_KEEPOPEN 0x1 +#define FILEDISP_REOPEN 0x2 +#define FILEDISP_IFEXISTS 0x4 +}; + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +static void HEIM_CALLCONV +log_file(heim_context context, const char *timestr, const char *msg, void *data) +{ + struct timeval tv; + struct file_data *f = data; + FILE *logf = f->fd; + char *msgclean; + size_t i = 0; + size_t j; + + if (f->filename && (logf == NULL || (f->disp & FILEDISP_REOPEN))) { + int flags = O_WRONLY|O_APPEND; + int fd; + + if (f->mode[0] == 'e') { + flags |= O_CLOEXEC; + i = 1; + } + if (f->mode[i] == 'w') + flags |= O_TRUNC; + if (f->mode[i + 1] == '+') + flags |= O_RDWR; + + if (f->disp & FILEDISP_IFEXISTS) { + /* Cache failure for 1s */ + gettimeofday(&tv, NULL); + if (tv.tv_sec == f->tv.tv_sec) + return; + } else { + flags |= O_CREAT; + } + + fd = open(f->filename, flags, 0666); /* umask best be set */ + if (fd == -1) { + if (f->disp & FILEDISP_IFEXISTS) + gettimeofday(&f->tv, NULL); + return; + } + rk_cloexec(fd); + logf = fdopen(fd, f->mode); + } + if (f->fd == NULL && (f->disp & FILEDISP_KEEPOPEN)) + f->fd = logf; + if (logf == NULL) + return; + /* + * make sure the log doesn't contain special chars: + * we used to use strvisx(3) to encode the log, but this is + * inconsistent with our syslog(3) code which does not do this. + * It also makes it inelegant to write data which has already + * been quoted such as what krb5_unparse_principal() gives us. + * So, we change here to eat the special characters, instead. + */ + if (msg && (msgclean = strdup(msg))) { + for (i = 0, j = 0; msg[i]; i++) + if (msg[i] >= 32 || msg[i] == '\t') + msgclean[j++] = msg[i]; + fprintf(logf, "%s %s\n", timestr ? timestr : "", msgclean); + free(msgclean); + } + if (logf != f->fd) + fclose(logf); +} + +static void HEIM_CALLCONV +close_file(void *data) +{ + struct file_data *f = data; + if (f->fd && f->fd != stdout && f->fd != stderr) + fclose(f->fd); + free(f->filename); + free(data); +} + +static heim_error_code +open_file(heim_context context, heim_log_facility *fac, int min, int max, + const char *filename, const char *mode, FILE *f, int disp, + int exp_tokens) +{ + heim_error_code ret = 0; + struct file_data *fd; + + if ((fd = calloc(1, sizeof(*fd))) == NULL) + return heim_enomem(context); + + fd->filename = NULL; + fd->mode = mode; + fd->fd = f; + fd->disp = disp; + + if (filename) { + if (exp_tokens) + ret = heim_expand_path_tokens(context, filename, 1, &fd->filename, NULL); + else if ((fd->filename = strdup(filename)) == NULL) + ret = heim_enomem(context); + } + if (ret == 0) + ret = heim_addlog_func(context, fac, min, max, log_file, close_file, fd); + if (ret) { + free(fd->filename); + free(fd); + } else if (disp & FILEDISP_KEEPOPEN) { + log_file(context, NULL, NULL, fd); + fd = NULL; + } + return ret; +} + +heim_error_code +heim_addlog_dest(heim_context context, heim_log_facility *f, const char *orig) +{ + heim_error_code ret = 0; + int min = 0, max = 3, n; + char c; + const char *p = orig; +#ifdef _WIN32 + const char *q; +#endif + + n = sscanf(p, "%d%c%d/", &min, &c, &max); + if (n == 2) { + if (ISPATHSEP(c)) { + if (min < 0) { + max = -min; + min = 0; + } else { + max = min; + } + } + if (c == '-') + max = -1; + } + if (n) { +#ifdef _WIN32 + q = strrchr(p, '\\'); + if (q != NULL) + p = q; + else +#endif + p = strchr(p, '/'); + if (p == NULL) { + heim_set_error_message(context, EINVAL /*XXX HEIM_ERR_LOG_PARSE*/, + N_("failed to parse \"%s\"", ""), orig); + return EINVAL /*XXX HEIM_ERR_LOG_PARSE*/; + } + p++; + } + if (strcmp(p, "STDERR") == 0) { + ret = open_file(context, f, min, max, NULL, "a", stderr, + FILEDISP_KEEPOPEN, 0); + } else if (strcmp(p, "CONSOLE") == 0) { + /* XXX WIN32 */ + ret = open_file(context, f, min, max, "/dev/console", "w", NULL, + FILEDISP_KEEPOPEN, 0); + } else if (strncmp(p, "EFILE:", 5) == 0) { + ret = open_file(context, f, min, max, p + sizeof("EFILE:") - 1, "a", + NULL, FILEDISP_IFEXISTS | FILEDISP_REOPEN, 1); + } else if (strncmp(p, "EFILE=", 5) == 0) { + ret = open_file(context, f, min, max, p + sizeof("EFILE=") - 1, "a", + NULL, FILEDISP_IFEXISTS | FILEDISP_KEEPOPEN, 1); + } else if (strncmp(p, "FILE:", sizeof("FILE:") - 1) == 0) { + ret = open_file(context, f, min, max, p + sizeof("FILE:") - 1, "a", + NULL, FILEDISP_REOPEN, 1); + } else if (strncmp(p, "FILE=", sizeof("FILE=") - 1) == 0) { + ret = open_file(context, f, min, max, p + sizeof("FILE=") - 1, "a", + NULL, FILEDISP_KEEPOPEN, 1); + } else if (strncmp(p, "DEVICE:", sizeof("DEVICE:") - 1) == 0) { + ret = open_file(context, f, min, max, p + sizeof("DEVICE:") - 1, "a", + NULL, FILEDISP_REOPEN, 0); + } else if (strncmp(p, "DEVICE=", sizeof("DEVICE=") - 1) == 0) { + ret = open_file(context, f, min, max, p + sizeof("DEVICE=") - 1, "a", + NULL, FILEDISP_KEEPOPEN, 0); + } else if (strncmp(p, "SYSLOG", 6) == 0 && (p[6] == '\0' || p[6] == ':')) { + char severity[128] = ""; + char facility[128] = ""; + p += 6; + if (*p != '\0') + p++; + if (strsep_copy(&p, ":", severity, sizeof(severity)) != -1) + strsep_copy(&p, ":", facility, sizeof(facility)); + if (*severity == '\0') + strlcpy(severity, "ERR", sizeof(severity)); + if (*facility == '\0') + strlcpy(facility, "AUTH", sizeof(facility)); + ret = open_syslog(context, f, min, max, severity, facility); + } else { + ret = EINVAL; /*XXX HEIM_ERR_LOG_PARSE*/ + heim_set_error_message(context, ret, + N_("unknown log type: %s", ""), p); + } + return ret; +} + +heim_error_code +heim_openlog(heim_context context, + const char *program, + const char **specs, + heim_log_facility **fac) +{ + heim_error_code ret; + + ret = heim_initlog(context, program, fac); + if (ret) + return ret; + + if (specs) { + size_t i; + for (i = 0; specs[i] && ret == 0; i++) + ret = heim_addlog_dest(context, *fac, specs[i]); + } else { + ret = heim_addlog_dest(context, *fac, "SYSLOG"); + } + return ret; +} + +void +heim_closelog(heim_context context, heim_log_facility *fac) +{ + int i; + + if (!fac || heim_base_atomic_dec_32(&fac->refs)) + return; + for (i = 0; i < fac->len; i++) + (*fac->val[i].close_func)(fac->val[i].data); + free(fac->val); + free(fac->program); + fac->val = NULL; + fac->len = 0; + fac->program = NULL; + free(fac); + return; +} + +static void +format_time(heim_context context, time_t t, char *s, size_t len) +{ + struct tm *tm = heim_context_get_log_utc(context) ? + gmtime(&t) : localtime(&t); + if (tm && strftime(s, len, heim_context_get_time_fmt(context), tm)) + return; + snprintf(s, len, "%ld", (long)t); +} + +#undef __attribute__ +#define __attribute__(X) + +heim_error_code +heim_vlog_msg(heim_context context, + heim_log_facility *fac, + char **reply, + int level, + const char *fmt, + va_list ap) +__attribute__ ((__format__ (__printf__, 5, 0))) +{ + + char *msg = NULL; + const char *actual = NULL; + char buf[64]; + time_t t = 0; + int i; + + if (!fac) + fac = context->log_dest; + for (i = 0; fac && i < fac->len; i++) + if (fac->val[i].min <= level && + (fac->val[i].max < 0 || fac->val[i].max >= level)) { + if (t == 0) { + t = time(NULL); + format_time(context, t, buf, sizeof(buf)); + } + if (actual == NULL) { + int ret = vasprintf(&msg, fmt, ap); + if (ret < 0 || msg == NULL) + actual = fmt; + else + actual = msg; + } + (*fac->val[i].log_func)(context, buf, actual, fac->val[i].data); + } + if (reply == NULL) + free(msg); + else + *reply = msg; + return 0; +} + +heim_error_code +heim_vlog(heim_context context, + heim_log_facility *fac, + int level, + const char *fmt, + va_list ap) +__attribute__ ((__format__ (__printf__, 4, 0))) +{ + return heim_vlog_msg(context, fac, NULL, level, fmt, ap); +} + +heim_error_code +heim_log_msg(heim_context context, + heim_log_facility *fac, + int level, + char **reply, + const char *fmt, + ...) +__attribute__ ((__format__ (__printf__, 5, 6))) +{ + va_list ap; + heim_error_code ret; + + va_start(ap, fmt); + ret = heim_vlog_msg(context, fac, reply, level, fmt, ap); + va_end(ap); + return ret; +} + + +heim_error_code +heim_log(heim_context context, + heim_log_facility *fac, + int level, + const char *fmt, + ...) +__attribute__ ((__format__ (__printf__, 4, 5))) +{ + va_list ap; + heim_error_code ret; + + va_start(ap, fmt); + ret = heim_vlog(context, fac, level, fmt, ap); + va_end(ap); + return ret; +} + +void +heim_debug(heim_context context, + int level, + const char *fmt, + ...) +__attribute__ ((__format__ (__printf__, 3, 4))) +{ + heim_log_facility *fac; + va_list ap; + + if (context == NULL || + (fac = heim_get_debug_dest(context)) == NULL) + return; + + va_start(ap, fmt); + heim_vlog(context, fac, level, fmt, ap); + va_end(ap); +} + +void +heim_vdebug(heim_context context, + int level, + const char *fmt, + va_list ap) +__attribute__ ((__format__ (__printf__, 3, 0))) +{ + heim_log_facility *fac; + + if (context == NULL || + (fac = heim_get_debug_dest(context)) == NULL) + return; + + heim_vlog(context, fac, level, fmt, ap); +} + +heim_error_code +heim_have_debug(heim_context context, int level) +{ + return (context != NULL && heim_get_debug_dest(context) != NULL); +} + +heim_error_code +heim_add_warn_dest(heim_context context, const char *program, + const char *log_spec) +{ + heim_log_facility *fac; + + heim_error_code ret; + + if ((fac = heim_get_warn_dest(context)) == NULL) { + ret = heim_initlog(context, program, &fac); + if (ret) + return ret; + heim_set_warn_dest(context, fac); + } + + ret = heim_addlog_dest(context, fac, log_spec); + if (ret) + return ret; + return 0; +} + +heim_error_code +heim_add_debug_dest(heim_context context, const char *program, + const char *log_spec) +{ + heim_log_facility *fac; + heim_error_code ret; + + if ((fac = heim_get_debug_dest(context)) == NULL) { + ret = heim_initlog(context, program, &fac); + if (ret) + return ret; + heim_set_debug_dest(context, fac); + } + + ret = heim_addlog_dest(context, fac, log_spec); + if (ret) + return ret; + return 0; +} + +struct heim_audit_kv_tuple { + heim_string_t key; + heim_object_t value; +}; + +static struct heim_audit_kv_tuple zero_tuple; + +static struct heim_audit_kv_tuple +fmtkv(int flags, const char *k, const char *fmt, va_list ap) + __attribute__ ((__format__ (__printf__, 3, 0))) +{ + size_t i; + ssize_t j; + struct heim_audit_kv_tuple kv; + char *value; + char *value_vis; + + j = vasprintf(&value, fmt, ap); + if (j < 0 || value == NULL) + return zero_tuple; + + /* We optionally eat the whitespace. */ + + if (flags & HEIM_SVC_AUDIT_EATWHITE) { + for (i=0, j=0; value[i]; i++) + if (value[i] != ' ' && value[i] != '\t') + value[j++] = value[i]; + value[j] = '\0'; + } + + if (flags & (HEIM_SVC_AUDIT_VIS | HEIM_SVC_AUDIT_VISLAST)) { + int vis_flags = VIS_CSTYLE | VIS_OCTAL | VIS_NL; + + if (flags & HEIM_SVC_AUDIT_VIS) + vis_flags |= VIS_WHITE; + value_vis = malloc((j + 1) * 4 + 1); + if (value_vis) + strvisx(value_vis, value, j, vis_flags); + free(value); + if (value_vis == NULL) + return zero_tuple; + } else + value_vis = value; + + if (k) + kv.key = heim_string_create(k); + else + kv.key = NULL; + kv.value = heim_string_ref_create(value_vis, free); + + return kv; +} + +void +heim_audit_vaddreason(heim_svc_req_desc r, const char *fmt, va_list ap) + __attribute__ ((__format__ (__printf__, 2, 0))) +{ + struct heim_audit_kv_tuple kv; + + kv = fmtkv(HEIM_SVC_AUDIT_VISLAST, NULL, fmt, ap); + if (kv.value == NULL) { + heim_log(r->hcontext, r->logf, 1, "heim_audit_vaddreason: " + "failed to add reason (out of memory)"); + return; + } + + heim_log(r->hcontext, r->logf, 7, "heim_audit_vaddreason(): " + "adding reason %s", heim_string_get_utf8(kv.value)); + if (r->reason) { + heim_string_t str2; + + str2 = heim_string_create_with_format("%s: %s", + heim_string_get_utf8(kv.value), + heim_string_get_utf8(r->reason)); + if (str2) { + heim_release(kv.value); + kv.value = str2; + } + } + heim_release(r->reason); + r->reason = kv.value; +} + +void +heim_audit_addreason(heim_svc_req_desc r, const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 2, 3))) +{ + va_list ap; + + va_start(ap, fmt); + heim_audit_vaddreason(r, fmt, ap); + va_end(ap); +} + +size_t +addkv(heim_svc_req_desc r, heim_object_t key, heim_object_t value) +{ + size_t index; + heim_object_t obj; + + obj = heim_dict_get_value(r->kv, key); + if (obj) { + if (heim_get_tid(obj) == HEIM_TID_ARRAY) { + index = heim_array_get_length(obj); + heim_array_append_value(obj, value); + } else { + heim_array_t array = heim_array_create(); + + index = 1; + heim_array_append_value(array, obj); + heim_array_append_value(array, value); + heim_dict_set_value(r->kv, key, array); + heim_release(array); /* retained by r->kv */ + } + } else { + index = 0; + heim_dict_set_value(r->kv, key, value); + } + + return index; +} + +/* + * add a key-value token. if the key already exists, the value is + * promoted to an array of values. + */ + +void +heim_audit_vaddkv(heim_svc_req_desc r, int flags, const char *k, + const char *fmt, va_list ap) + __attribute__ ((__format__ (__printf__, 4, 0))) +{ + struct heim_audit_kv_tuple kv; + size_t index; + + kv = fmtkv(flags, k, fmt, ap); + if (kv.key == NULL || kv.value == NULL) { + heim_log(r->hcontext, r->logf, 1, "heim_audit_vaddkv: " + "failed to add kv pair (out of memory)"); + heim_release(kv.key); + heim_release(kv.value); + return; + } + + index = addkv(r, kv.key, kv.value); + + heim_log(r->hcontext, r->logf, 7, "heim_audit_vaddkv(): " + "kv pair[%zu] %s=%s", index, + heim_string_get_utf8(kv.key), heim_string_get_utf8(kv.value)); + + heim_release(kv.key); + heim_release(kv.value); +} + +void +heim_audit_addkv(heim_svc_req_desc r, int flags, const char *k, + const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 4, 5))) +{ + va_list ap; + + va_start(ap, fmt); + heim_audit_vaddkv(r, flags, k, fmt, ap); + va_end(ap); +} + +void +heim_audit_addkv_timediff(heim_svc_req_desc r, const char *k, + const struct timeval *start, + const struct timeval *end) +{ + time_t sec; + int usec; + const char *sign = ""; + + if (end->tv_sec > start->tv_sec || + (end->tv_sec == start->tv_sec && end->tv_usec >= start->tv_usec)) { + sec = end->tv_sec - start->tv_sec; + usec = end->tv_usec - start->tv_usec; + } else { + sec = start->tv_sec - end->tv_sec; + usec = start->tv_usec - end->tv_usec; + sign = "-"; + } + + if (usec < 0) { + usec += 1000000; + sec -= 1; + } + + heim_audit_addkv(r, 0, k, "%s%ld.%06d", sign, (long)sec, usec); +} + +void +heim_audit_setkv_bool(heim_svc_req_desc r, const char *k, int v) +{ + heim_string_t key = heim_string_create(k); + heim_number_t value; + + if (key == NULL) + return; + + heim_log(r->hcontext, r->logf, 7, "heim_audit_setkv_bool(): " + "setting kv pair %s=%s", k, v ? "true" : "false"); + + value = heim_bool_create(v); + heim_dict_set_value(r->kv, key, value); + heim_release(key); + heim_release(value); +} + +void +heim_audit_addkv_number(heim_svc_req_desc r, const char *k, int64_t v) +{ + heim_string_t key = heim_string_create(k); + heim_number_t value; + + if (key == NULL) + return; + + heim_log(r->hcontext, r->logf, 7, "heim_audit_addkv_number(): " + "adding kv pair %s=%lld", k, (long long)v); + + value = heim_number_create(v); + addkv(r, key, value); + heim_release(key); + heim_release(value); +} + +void +heim_audit_setkv_number(heim_svc_req_desc r, const char *k, int64_t v) +{ + heim_string_t key = heim_string_create(k); + heim_number_t value; + + if (key == NULL) + return; + + heim_log(r->hcontext, r->logf, 7, "heim_audit_setkv_number(): " + "setting kv pair %s=%lld", k, (long long)v); + + value = heim_number_create(v); + heim_dict_set_value(r->kv, key, value); + heim_release(key); + heim_release(value); +} + +void +heim_audit_addkv_object(heim_svc_req_desc r, const char *k, heim_object_t value) +{ + heim_string_t key = heim_string_create(k); + heim_string_t descr; + + if (key == NULL) + return; + + descr = heim_json_copy_serialize(value, HEIM_JSON_F_NO_DATA_DICT, NULL); + heim_log(r->hcontext, r->logf, 7, "heim_audit_addkv_object(): " + "adding kv pair %s=%s", + k, descr ? heim_string_get_utf8(descr) : ""); + addkv(r, key, value); + heim_release(key); + heim_release(descr); +} + +void +heim_audit_setkv_object(heim_svc_req_desc r, const char *k, heim_object_t value) +{ + heim_string_t key = heim_string_create(k); + heim_string_t descr; + + if (key == NULL) + return; + + descr = heim_json_copy_serialize(value, HEIM_JSON_F_NO_DATA_DICT, NULL); + heim_log(r->hcontext, r->logf, 7, "heim_audit_setkv_object(): " + "setting kv pair %s=%s", + k, descr ? heim_string_get_utf8(descr) : ""); + heim_dict_set_value(r->kv, key, value); + heim_release(key); + heim_release(descr); +} + +heim_object_t +heim_audit_getkv(heim_svc_req_desc r, const char *k) +{ + heim_string_t key; + heim_object_t value; + + key = heim_string_create(k); + if (key == NULL) + return NULL; + + value = heim_dict_get_value(r->kv, key); + heim_release(key); + return value; +} + +struct heim_audit_kv_buf { + char buf[1024]; + size_t pos; + heim_object_t iter; +}; + +static void +audit_trail_iterator(heim_object_t key, heim_object_t value, void *arg); + +static void +audit_trail_iterator_array(heim_object_t value, void *arg, int *stop) +{ + struct heim_audit_kv_buf *kvb = arg; + + audit_trail_iterator(kvb->iter, value, kvb); +} + +static void +audit_trail_iterator(heim_object_t key, heim_object_t value, void *arg) +{ + struct heim_audit_kv_buf *kvb = arg; + char num[32]; + const char *k = heim_string_get_utf8(key), *v = NULL; + char *b64 = NULL; + + if (k == NULL || *k == '#') /* # keys are hidden */ + return; + + switch (heim_get_tid(value)) { + case HEIM_TID_STRING: + v = heim_string_get_utf8(value); + break; + case HEIM_TID_NUMBER: + snprintf(num, sizeof(num), "%lld", (long long)heim_number_get_long(value)); + v = num; + break; + case HEIM_TID_NULL: + v = "null"; + break; + case HEIM_TID_BOOL: + v = heim_bool_val(value) ? "true" : "false"; + break; + case HEIM_TID_ARRAY: + if (kvb->iter) + break; /* arrays cannot be nested */ + + kvb->iter = key; + heim_array_iterate_f(value, kvb, audit_trail_iterator_array); + kvb->iter = NULL; + break; + case HEIM_TID_DATA: { + const heim_octet_string *data = heim_data_get_data(value); + if (rk_base64_encode(data->data, data->length, &b64) >= 0) + v = b64; + break; + } + default: + break; + } + + if (v == NULL) + return; + + if (kvb->pos < sizeof(kvb->buf) - 1) + kvb->buf[kvb->pos++] = ' '; + for (; *k && kvb->pos < sizeof(kvb->buf) - 1; kvb->pos++) + kvb->buf[kvb->pos] = *k++; + if (kvb->pos < sizeof(kvb->buf) - 1) + kvb->buf[kvb->pos++] = '='; + for (; *v && kvb->pos < sizeof(kvb->buf) - 1; kvb->pos++) + kvb->buf[kvb->pos] = *v++; + + free(b64); +} + +void +heim_audit_trail(heim_svc_req_desc r, heim_error_code ret, const char *retname) +{ + const char *retval; + struct heim_audit_kv_buf kvb; + char retvalbuf[30]; /* Enough for UNKNOWN-%d */ + +#define CASE(x) case x : retval = #x; break + if (retname) { + retval = retname; + } else switch (ret ? ret : r->error_code) { + CASE(ENOMEM); + CASE(ENOENT); + CASE(EACCES); + case 0: + retval = "SUCCESS"; + break; + default: + /* Wish we had a com_err number->symbolic name function */ + (void) snprintf(retvalbuf, sizeof(retvalbuf), "UNKNOWN-%d", + ret ? ret : r->error_code); + retval = retvalbuf; + break; + } + + heim_audit_addkv_timediff(r, "elapsed", &r->tv_start, &r->tv_end); + if (r->e_text && r->kv) + heim_audit_addkv(r, HEIM_SVC_AUDIT_VIS, "e-text", "%s", r->e_text); + + memset(&kvb, 0, sizeof(kvb)); + if (r->kv) + heim_dict_iterate_f(r->kv, &kvb, audit_trail_iterator); + kvb.buf[kvb.pos] = '\0'; + + heim_log(r->hcontext, r->logf, 3, "%s %s %s %s %s%s%s%s", + r->reqtype, retval, r->from, + r->cname ? r->cname : "", + r->sname ? r->sname : "", + kvb.buf, r->reason ? " reason=" : "", + r->reason ? heim_string_get_utf8(r->reason) : ""); +} diff --git a/third_party/heimdal/lib/base/null.c b/third_party/heimdal/lib/base/null.c new file mode 100644 index 0000000..43bde96 --- /dev/null +++ b/third_party/heimdal/lib/base/null.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" + +struct heim_type_data _heim_null_object = { + HEIM_TID_NULL, + "null-object", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +heim_null_t +heim_null_create(void) +{ + return heim_base_make_tagged_object(0, HEIM_TID_NULL); +} diff --git a/third_party/heimdal/lib/base/number.c b/third_party/heimdal/lib/base/number.c new file mode 100644 index 0000000..8833c8b --- /dev/null +++ b/third_party/heimdal/lib/base/number.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" + +static void HEIM_CALLCONV +number_dealloc(void *ptr) +{ +} + +static int +number_cmp(void *a, void *b) +{ + int na, nb; + + if (heim_base_is_tagged_object(a)) + na = heim_base_tagged_object_value(a); + else + na = *(int *)a; + + if (heim_base_is_tagged_object(b)) + nb = heim_base_tagged_object_value(b); + else + nb = *(int *)b; + + return na - nb; +} + +static uintptr_t +number_hash(void *ptr) +{ + if (heim_base_is_tagged_object(ptr)) + return heim_base_tagged_object_value(ptr); + return (uintptr_t)*(int64_t *)ptr; +} + +struct heim_type_data _heim_number_object = { + HEIM_TID_NUMBER, + "number-object", + NULL, + number_dealloc, + NULL, + number_cmp, + number_hash, + NULL +}; + +/** + * Create a number object + * + * @param the number to contain in the object + * + * @return a number object + */ + +heim_number_t +heim_number_create(int64_t number) +{ + heim_number_t n; + + if (number < 0xffffff && number >= 0) + return heim_base_make_tagged_object(number, HEIM_TID_NUMBER); + + n = _heim_alloc_object(&_heim_number_object, sizeof(int64_t)); + if (n) + *((int64_t *)n) = number; + return n; +} + +/** + * Return the type ID of number objects + * + * @return type id of number objects + */ + +heim_tid_t +heim_number_get_type_id(void) +{ + return HEIM_TID_NUMBER; +} + +/** + * Get the int value of the content + * + * @param number the number object to get the value from + * + * @return an int + */ + +int +heim_number_get_int(heim_number_t number) +{ + if (heim_base_is_tagged_object(number)) + return heim_base_tagged_object_value(number); + return (int)(*(int64_t *)number); +} + +int64_t +heim_number_get_long(heim_number_t number) +{ + if (heim_base_is_tagged_object(number)) + return heim_base_tagged_object_value(number); + return *(int64_t *)number; +} diff --git a/third_party/heimdal/lib/base/plugin.c b/third_party/heimdal/lib/base/plugin.c new file mode 100644 index 0000000..87bb5f7 --- /dev/null +++ b/third_party/heimdal/lib/base/plugin.c @@ -0,0 +1,785 @@ +/* + * Copyright (c) 2006 - 2020 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2018 AuriStor, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" +#include "common_plugin.h" + +/* + * Documentation for the Heimdal plugin system is in lib/krb5/plugin.c and + * lib/krb5/krb5-plugin.7. + */ + +/* + * Definitions: + * + * module - a category of plugin module, identified by subsystem + * (e.g., "krb5") + * dso - a library for a module containing a map of plugin + * types to plugins (e.g. "service_locator") + * plugin - a set of callbacks and state that follows the + * common plugin module definition (version, init, fini) + * + * Obviously it would have been clearer to use the term "module" rather than + * "DSO" given there is an internal "DSO", but "module" was already taken... + * + * modules := { module: dsos } + * dsos := { path, dsohandle, plugins-by-name } + * plugins-by-name := { plugin-name: [plug] } + * plug := { ftable, ctx } + */ + +/* global module use, use copy_modules() accessor to access */ +static heim_dict_t __modules; + +static HEIMDAL_MUTEX modules_mutex = HEIMDAL_MUTEX_INITIALIZER; + +static void +copy_modules_once(void *context) +{ + heim_dict_t *modules = (heim_dict_t *)context; + + *modules = heim_dict_create(11); + heim_assert(*modules, "plugin modules array allocation failure"); +} + +/* returns global modules list, refcount +1 */ +static heim_dict_t +copy_modules(void) +{ + static heim_base_once_t modules_once = HEIM_BASE_ONCE_INIT; + + heim_base_once_f(&modules_once, &__modules, copy_modules_once); + + return heim_retain(__modules); +} + +/* returns named module, refcount +1 */ +static heim_dict_t +copy_module(const char *name) +{ + heim_string_t module_name = heim_string_create(name); + heim_dict_t modules = copy_modules(); + heim_dict_t module; + + module = heim_dict_copy_value(modules, module_name); + if (module == NULL) { + module = heim_dict_create(11); + heim_dict_set_value(modules, module_name, module); + } + + heim_release(modules); + heim_release(module_name); + + return module; +} + +/* DSO helpers */ +struct heim_dso { + heim_string_t path; + heim_dict_t plugins_by_name; + void *dsohandle; +}; + +static void HEIM_CALLCONV +dso_dealloc(void *ptr) +{ + struct heim_dso *p = ptr; + + heim_release(p->path); + heim_release(p->plugins_by_name); +#ifdef HAVE_DLOPEN + if (p->dsohandle) + dlclose(p->dsohandle); +#endif +} + +/* returns internal "DSO" for name, refcount +1 */ +static struct heim_dso * +copy_internal_dso(const char *name) +{ + heim_string_t dso_name = HSTR("__HEIMDAL_INTERNAL_DSO__"); + heim_dict_t module = copy_module(name); + struct heim_dso *dso; + + if (module == NULL) + return NULL; + + dso = heim_dict_copy_value(module, dso_name); + if (dso == NULL) { + dso = heim_alloc(sizeof(*dso), "heim-dso", dso_dealloc); + + dso->path = dso_name; + dso->plugins_by_name = heim_dict_create(11); + + heim_dict_set_value(module, dso_name, dso); + } + + heim_release(module); + + return dso; +} + +struct heim_plugin { + heim_plugin_common_ftable_const_p ftable; + void *ctx; +}; + +static void HEIM_CALLCONV +plugin_free(void *ptr) +{ + struct heim_plugin *pl = ptr; + + if (pl->ftable && pl->ftable->fini) + pl->ftable->fini(pl->ctx); +} + +struct heim_plugin_register_ctx { + const void *symbol; + int is_dup; +}; + +static void +plugin_register_check_dup(heim_object_t value, void *ctx, int *stop) +{ + struct heim_plugin_register_ctx *pc = ctx; + struct heim_plugin *pl = value; + + if (pl->ftable == pc->symbol) { + pc->is_dup = 1; + *stop = 1; + } +} + +/** + * Register a plugin symbol name of specific type. + * @param context a Keberos context + * @param module name of plugin module (e.g., "krb5") + * @param name name of plugin symbol (e.g., "krb5_plugin_kuserok") + * @param ftable a pointer to a function pointer table + * @return In case of error a non zero error com_err error is returned + * and the Kerberos error string is set. + * + * @ingroup heim_support + */ + +heim_error_code +heim_plugin_register(heim_context context, + heim_pcontext pcontext, + const char *module, + const char *name, + const void *ftable) +{ + heim_error_code ret; + heim_array_t plugins; + heim_string_t hname; + struct heim_dso *dso; + struct heim_plugin_register_ctx ctx; + + ctx.symbol = ftable; + ctx.is_dup = 0; + + HEIMDAL_MUTEX_lock(&modules_mutex); + + dso = copy_internal_dso(module); + hname = heim_string_create(name); + plugins = heim_dict_copy_value(dso->plugins_by_name, hname); + if (plugins != NULL) + heim_array_iterate_f(plugins, &ctx, plugin_register_check_dup); + else { + plugins = heim_array_create(); + heim_dict_set_value(dso->plugins_by_name, hname, plugins); + } + + ret = 0; + if (!ctx.is_dup) { + /* Note: refactored plugin API only supports common plugin layout */ + struct heim_plugin *pl; + + pl = heim_alloc(sizeof(*pl), "heim-plugin", plugin_free); + if (pl == NULL) { + ret = heim_enomem(context); + } else { + pl->ftable = ftable; + ret = pl->ftable->init(pcontext, &pl->ctx); + if (ret == 0) { + heim_array_append_value(plugins, pl); + heim_debug(context, 5, "Registered %s plugin", name); + } + heim_release(pl); + } + } + + HEIMDAL_MUTEX_unlock(&modules_mutex); + + heim_release(dso); + heim_release(hname); + heim_release(plugins); + + return ret; +} + +#ifdef HAVE_DLOPEN + +static char * +resolve_origin(const char *di, const char *module) +{ +#ifdef HAVE_DLADDR + Dl_info dl_info; + const char *dname; + char *path, *p; + + if (strncmp(di, "$ORIGIN/", sizeof("$ORIGIN/") - 1) != 0 && + strcmp(di, "$ORIGIN") != 0) + return strdup(di); + + di += sizeof("$ORIGIN") - 1; + + if (dladdr(heim_plugin_register, &dl_info) == 0) { + char *s = NULL; + + /* dladdr() failed */ + if (asprintf(&s, LIBDIR "/plugin/%s", module) == -1) + return NULL; + return s; + } + + dname = dl_info.dli_fname; +#ifdef _WIN32 + p = strrchr(dname, '\\'); + if (p == NULL) +#endif + p = strrchr(dname, '/'); + if (p) { + if (asprintf(&path, "%.*s%s", (int) (p - dname), dname, di) == -1) + return NULL; + } else { + if (asprintf(&path, "%s%s", dname, di) == -1) + return NULL; + } + + return path; +#else + char *s = NULL; + + if (strncmp(di, "$ORIGIN/", sizeof("$ORIGIN/") - 1) != 0 && + strcmp(di, "$ORIGIN") != 0) + return strdup(di); + if (asprintf(&s, LIBDIR "/plugin/%s", module) == -1) + return NULL; + return s; +#endif /* HAVE_DLADDR */ +} + +#endif /* HAVE_DLOPEN */ + +/** + * Load plugins (new system) for the given module @module from the given + * directory @paths. + * + * Inputs: + * + * @context A heim_context + * @module Name of plugin module (typically "krb5") + * @paths Array of directory paths where to look + */ +void +heim_load_plugins(heim_context context, + const char *module, + const char **paths) +{ +#ifdef HAVE_DLOPEN + heim_string_t s = heim_string_create(module); + heim_dict_t mod, modules; + struct dirent *entry; + heim_error_code ret; + const char **di; + char *dirname = NULL; + DIR *d; +#ifdef _WIN32 + char *plugin_prefix; + size_t plugin_prefix_len; + + if (asprintf(&plugin_prefix, "plugin_%s_", module) == -1) + return; + plugin_prefix_len = (plugin_prefix ? strlen(plugin_prefix) : 0); +#endif + + HEIMDAL_MUTEX_lock(&modules_mutex); + + modules = copy_modules(); + + mod = heim_dict_copy_value(modules, s); + if (mod == NULL) { + mod = heim_dict_create(11); + if (mod == NULL) { + HEIMDAL_MUTEX_unlock(&modules_mutex); + heim_release(s); + heim_release(modules); + heim_debug(context, 5, "Load plugins for module %s failed", module); + return; + } + heim_dict_set_value(modules, s, mod); + } + heim_release(s); + heim_release(modules); + + for (di = paths; *di != NULL; di++) { + free(dirname); + dirname = resolve_origin(*di, module); + if (dirname == NULL) { + heim_debug(context, 10, "Could not resolve %s", *di); + continue; + } + d = opendir(dirname); + if (d == NULL) { + heim_debug(context, 10, "No such directory %s", dirname); + continue; + } + rk_cloexec_dir(d); + + heim_debug(context, 10, "Load plugins for module %s; search %s (%s)", + module, *di, dirname); + + while ((entry = readdir(d)) != NULL) { + char *n = entry->d_name; + char *path = NULL; + heim_string_t spath; + struct heim_dso *p; + + /* skip . and .. */ + if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) + continue; + + ret = 0; +#ifdef _WIN32 + /* + * On Windows, plugins must be loaded from the same directory as + * heimdal.dll (typically the assembly directory) and must have + * the name form "plugin__.dll". + */ + { + char *ext; + + if (strnicmp(n, plugin_prefix, plugin_prefix_len) != 0) + continue; + ext = strrchr(n, '.'); + if (ext == NULL || stricmp(ext, ".dll") != 0) + continue; + + ret = asprintf(&path, "%s\\%s", dirname, n); + if (ret < 0 || path == NULL) + continue; + } +#endif +#ifdef __APPLE__ + { /* support loading bundles on MacOS */ + size_t len = strlen(n); + if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0) + ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dirname, n, (int)(len - 7), n); + } +#endif + if (ret < 0 || path == NULL) + ret = asprintf(&path, "%s/%s", dirname, n); + + if (ret < 0 || path == NULL) + continue; + + spath = heim_string_create(n); + if (spath == NULL) { + free(path); + continue; + } + + /* check if already cached */ + p = heim_dict_copy_value(mod, spath); + if (p == NULL) { + p = heim_alloc(sizeof(*p), "heim-dso", dso_dealloc); + if (p) + p->dsohandle = dlopen(path, RTLD_LOCAL|RTLD_LAZY|RTLD_GROUP); + if (p && p->dsohandle) { + p->path = heim_retain(spath); + p->plugins_by_name = heim_dict_create(11); + heim_dict_set_value(mod, spath, p); + heim_debug(context, 10, "Load plugins for module %s; " + "found DSO %s", module, path); + } + } + heim_release(p); + heim_release(spath); + free(path); + } + closedir(d); + } + free(dirname); + HEIMDAL_MUTEX_unlock(&modules_mutex); + heim_release(mod); +#ifdef _WIN32 + if (plugin_prefix) + free(plugin_prefix); +#endif +#endif /* HAVE_DLOPEN */ +} + +/** + * Unload plugins of the given @module name. + * + * Params: + * + * @module Name of module whose plusins to unload. + */ +void +heim_unload_plugins(heim_context context, const char *module) +{ + heim_string_t sname = heim_string_create(module); + heim_dict_t modules; + + HEIMDAL_MUTEX_lock(&modules_mutex); + + modules = copy_modules(); + heim_dict_delete_key(modules, sname); + + HEIMDAL_MUTEX_unlock(&modules_mutex); + + heim_release(modules); + heim_release(sname); +} + +struct iter_ctx { + heim_context context; + heim_pcontext pcontext; + heim_string_t n; + const struct heim_plugin_data *caller; + int flags; + heim_array_t result; + int32_t (HEIM_LIB_CALL *func)(void *, const void *, void *, void *); + void *userctx; + int32_t ret; + int32_t plugin_no_handle_retval; +}; + +#ifdef HAVE_DLOPEN +/* + * Add plugin from a DSO that exports the plugin structure directly. This is + * provided for backwards compatibility with prior versions of Heimdal, but it + * does not allow a module to export multiple plugins, nor does it allow + * instance validation. + */ +static heim_array_t +add_dso_plugin_struct(heim_context context, + heim_pcontext pcontext, + const char *dsopath, + void *dsohandle, + const char *name) +{ + heim_error_code ret; + heim_plugin_common_ftable_p cpm; + struct heim_plugin *pl; + heim_array_t plugins; + + if (dsohandle == NULL) + return NULL; + + /* suppress error here because we may be looking for a different plugin type */ + cpm = (heim_plugin_common_ftable_p)dlsym(dsohandle, name); + if (cpm == NULL) { + heim_debug(context, 15, "Symbol %s not found in %s", name, dsopath); + return NULL; + } + + heim_warnx(context, "plugin %s uses deprecated loading mechanism", dsopath); + + pl = heim_alloc(sizeof(*pl), "heim-plugin", plugin_free); + + ret = cpm->init(pcontext, &pl->ctx); + if (ret) { + heim_warn(context, ret, "plugin %s failed to initialize", dsopath); + heim_release(pl); + return NULL; + } + + pl->ftable = cpm; + + plugins = heim_array_create(); + heim_array_append_value(plugins, pl); + heim_release(pl); + + return plugins; +} + +static int +validate_plugin_deps(heim_context context, + const struct heim_plugin_data *caller, + const char *dsopath, + heim_get_instance_func_t get_instance) +{ + size_t i; + + if (get_instance == NULL) { + heim_warnx(context, "plugin %s omitted instance callback", + dsopath); + return FALSE; + } + + for (i = 0; caller->deps[i] != NULL; i++) { + uintptr_t heim_instance, plugin_instance; + + heim_instance = caller->get_instance(caller->deps[i]); + plugin_instance = get_instance(caller->deps[i]); + + if (heim_instance == 0 || plugin_instance == 0) + continue; + + if (heim_instance != plugin_instance) { + heim_warnx(context, "plugin %s library %s linked against different " + "instance of Heimdal (got %zu, us %zu)", + dsopath, caller->deps[i], + plugin_instance, heim_instance); + return FALSE; + } + heim_debug(context, 10, "Validated plugin library dependency %s for %s", + caller->deps[i], dsopath); + } + + return TRUE; +} + +/* + * New interface from Heimdal 8 where a DSO can export a load function + * that can return both a Heimdal instance identifier along with an + * array of plugins. + */ +static heim_array_t +add_dso_plugins_load_fn(heim_context context, + heim_pcontext pcontext, + const struct heim_plugin_data *caller, + const char *dsopath, + void *dsohandle) +{ + heim_error_code ret; + heim_array_t plugins; + heim_plugin_load_t load_fn; + char *sym = NULL; + size_t i; + heim_get_instance_func_t get_instance; + size_t n_ftables; + heim_plugin_common_ftable_cp *ftables; + + if (asprintf(&sym, "%s_plugin_load", caller->name) == -1 || sym == NULL) + return NULL; + + /* suppress error here because we may be looking for a different plugin type */ + load_fn = (heim_plugin_load_t)dlsym(dsohandle, sym); + if (load_fn == NULL) { + heim_debug(context, 15, "Symbol %s not found in %s", sym, dsopath); + free(sym); + return NULL; + } + + ret = load_fn(pcontext, &get_instance, &n_ftables, &ftables); + if (ret) { + heim_warn(context, ret, "plugin %s failed to load", dsopath); + free(sym); + + /* fallback to loading structure directly */ + return add_dso_plugin_struct(context, pcontext, dsopath, + dsohandle, caller->name); + } + + if (!validate_plugin_deps(context, caller, dsopath, get_instance)) { + free(sym); + return NULL; + } + + plugins = heim_array_create(); + + for (i = 0; i < n_ftables; i++) { + heim_plugin_common_ftable_cp cpm = ftables[i]; + struct heim_plugin *pl; + + pl = heim_alloc(sizeof(*pl), "heim-plugin", plugin_free); + + ret = cpm->init(pcontext, &pl->ctx); + if (ret) { + heim_warn(context, ret, "plugin %s[%zu] failed to initialize", + dsopath, i); + } else { + pl->ftable = cpm; + heim_array_append_value(plugins, pl); + } + heim_release(pl); + } + + heim_debug(context, 15, "DSO %s loaded (%s)", dsopath, sym); + free(sym); + return plugins; +} +#endif /* HAVE_DLOPEN */ + +static void +reduce_by_version(heim_object_t value, void *ctx, int *stop) +{ + struct iter_ctx *s = ctx; + struct heim_plugin *pl = value; + + if (pl->ftable && pl->ftable->minor_version >= s->caller->min_version) + heim_array_append_value(s->result, pl); +} + +static void +search_modules(heim_object_t key, heim_object_t value, void *ctx) +{ + struct iter_ctx *s = ctx; + struct heim_dso *p = value; + heim_array_t plugins = heim_dict_copy_value(p->plugins_by_name, s->n); + +#ifdef HAVE_DLOPEN + if (plugins == NULL && p->dsohandle) { + const char *path = heim_string_get_utf8(p->path); + + plugins = add_dso_plugins_load_fn(s->context, + s->pcontext, + s->caller, + path, + p->dsohandle); + if (plugins) { + heim_dict_set_value(p->plugins_by_name, s->n, plugins); + heim_debug(s->context, 5, "Loaded %zu %s %s plugin%s from %s", + heim_array_get_length(plugins), + s->caller->module, s->caller->name, + heim_array_get_length(plugins) > 1 ? "s" : "", + path); + } + } +#endif /* HAVE_DLOPEN */ + + if (plugins) { + heim_array_iterate_f(plugins, s, reduce_by_version); + heim_release(plugins); + } +} + +static void +eval_results(heim_object_t value, void *ctx, int *stop) +{ + struct heim_plugin *pl = value; + struct iter_ctx *s = ctx; + + if (s->ret != s->plugin_no_handle_retval) + return; + + s->ret = s->func(s->pcontext, pl->ftable, pl->ctx, s->userctx); + if (s->ret != s->plugin_no_handle_retval + && !(s->flags & HEIM_PLUGIN_INVOKE_ALL)) + *stop = 1; +} + +/** + * Run plugins for the given @module (e.g., "krb5") and @name (e.g., + * "kuserok"). Specifically, the @func is invoked once per-plugin with + * four arguments: the @context, the plugin symbol value (a pointer to a + * struct whose first three fields are the same as common_plugin_ftable), + * a context value produced by the plugin's init method, and @userctx. + * + * @func should unpack arguments for a plugin function and invoke it + * with arguments taken from @userctx. @func should save plugin + * outputs, if any, in @userctx. + * + * All loaded and registered plugins are invoked via @func until @func + * returns something other than @nohandle. Plugins that have nothing to + * do for the given arguments should return the same value as @nohandle. + * + * Inputs: + * + * @context A heim_context + * @pcontext A context for the plugin, such as a krb5_context + * @module Name of module (typically "krb5") + * @name Name of pluggable interface (e.g., "kuserok") + * @min_version Lowest acceptable plugin minor version number + * @flags Flags (none defined at this time) + * @nohandle Flags (none defined at this time) + * @userctx Callback data for the callback function @func + * @func A callback function, invoked once per-plugin + * + * Outputs: None, other than the return value and such outputs as are + * gathered by @func. + */ +heim_error_code +heim_plugin_run_f(heim_context context, + heim_pcontext pcontext, + const struct heim_plugin_data *caller, + int flags, + int32_t nohandle, + void *userctx, + int32_t (HEIM_LIB_CALL *func)(void *, const void *, void *, void *)) +{ + heim_string_t m = heim_string_create(caller->module); + heim_dict_t modules, dict = NULL; + struct iter_ctx s; + + s.context = context; + s.pcontext = pcontext; + s.caller = caller; + s.n = heim_string_create(caller->name); + s.flags = flags; + s.result = heim_array_create(); + s.func = func; + s.userctx = userctx; + s.plugin_no_handle_retval = nohandle; + s.ret = nohandle; + + HEIMDAL_MUTEX_lock(&modules_mutex); + + /* Get loaded plugins */ + modules = copy_modules(); + dict = heim_dict_copy_value(modules, m); + + /* Add loaded plugins to s.result array */ + if (dict) + heim_dict_iterate_f(dict, &s, search_modules); + + /* We don't need to hold modules_mutex during plugin invocation */ + HEIMDAL_MUTEX_unlock(&modules_mutex); + + /* Invoke loaded plugins */ + heim_array_iterate_f(s.result, &s, eval_results); + + heim_release(s.result); + heim_release(s.n); + heim_release(dict); + heim_release(m); + heim_release(modules); + + return s.ret; +} diff --git a/third_party/heimdal/lib/base/roken_rename.h b/third_party/heimdal/lib/base/roken_rename.h new file mode 100644 index 0000000..ea72098 --- /dev/null +++ b/third_party/heimdal/lib/base/roken_rename.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 1998 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* $Id$ */ + +#ifndef __heimbase_roken_rename_h__ +#define __heimbase_roken_rename_h__ + +#ifndef HAVE_VSNPRINTF +#define rk_vsnprintf heimbase_vsnprintf +#endif +#ifndef HAVE_ASPRINTF +#define rk_asprintf heimbase_asprintf +#endif +#ifndef HAVE_ASNPRINTF +#define rk_asnprintf heimbase_asnprintf +#endif +#ifndef HAVE_VASPRINTF +#define rk_vasprintf heimbase_vasprintf +#endif +#ifndef HAVE_VASNPRINTF +#define rk_vasnprintf heimbase_vasnprintf +#endif +#ifndef HAVE_STRDUP +#define rk_strdup heimbase_strdup +#endif +#ifndef HAVE_STRNDUP +#define rk_strndup heimbase_strndup +#endif + +#endif /* __heimbase_roken_rename_h__ */ diff --git a/third_party/heimdal/lib/base/string.c b/third_party/heimdal/lib/base/string.c new file mode 100644 index 0000000..5e79e00 --- /dev/null +++ b/third_party/heimdal/lib/base/string.c @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2010 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "baselocl.h" +#include + +static void HEIM_CALLCONV +string_dealloc(void *ptr) +{ + heim_string_t s = ptr; + heim_string_free_f_t *deallocp; + heim_string_free_f_t dealloc; + + if (*(const char *)ptr != '\0') + return; + + /* Possible string ref */ + deallocp = _heim_get_isaextra(s, 0); + dealloc = *deallocp; + if (dealloc != NULL) { + char **strp = _heim_get_isaextra(s, 1); + dealloc(*strp); + } +} + +static int +string_cmp(void *a, void *b) +{ + if (*(char *)a == '\0') { + char **strp = _heim_get_isaextra(a, 1); + + if (*strp != NULL) + a = *strp; /* a is a string ref */ + } + if (*(char *)b == '\0') { + char **strp = _heim_get_isaextra(b, 1); + + if (*strp != NULL) + b = *strp; /* b is a string ref */ + } + return strcmp(a, b); +} + +static uintptr_t +string_hash(void *ptr) +{ + const char *s = ptr; + uintptr_t n; + + for (n = 0; *s; ++s) + n += *s; + return n; +} + +struct heim_type_data _heim_string_object = { + HEIM_TID_STRING, + "string-object", + NULL, + string_dealloc, + NULL, + string_cmp, + string_hash, + NULL +}; + +/** + * Create a string object + * + * @param string the string to create, must be an utf8 string + * + * @return string object + */ + +heim_string_t +heim_string_create(const char *string) +{ + return heim_string_create_with_bytes(string, strlen(string)); +} + +/** + * Create a string object without copying the source. + * + * @param string the string to referenced, must be UTF-8 + * @param dealloc the function to use to release the referece to the string + * + * @return string object + */ + +heim_string_t +heim_string_ref_create(const char *string, heim_string_free_f_t dealloc) +{ + heim_string_t s; + heim_string_free_f_t *deallocp; + + s = _heim_alloc_object(&_heim_string_object, 1); + if (s) { + const char **strp; + + ((char *)s)[0] = '\0'; + deallocp = _heim_get_isaextra(s, 0); + *deallocp = dealloc; + strp = _heim_get_isaextra(s, 1); + *strp = string; + } + return s; +} + +/** + * Create a string object + * + * @param string the string to create, must be an utf8 string + * @param len the length of the string + * + * @return string object + */ + +heim_string_t +heim_string_create_with_bytes(const void *data, size_t len) +{ + heim_string_t s; + + s = _heim_alloc_object(&_heim_string_object, len + 1); + if (s) { + if (len) + memcpy(s, data, len); + ((char *)s)[len] = '\0'; + } + return s; +} + +/** + * Create a string object using a format string + * + * @param fmt format string + * @param ... + * + * @return string object + */ + +heim_string_t +heim_string_create_with_format(const char *fmt, ...) +{ + heim_string_t s; + char *str = NULL; + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vasprintf(&str, fmt, ap); + va_end(ap); + if (ret < 0 || str == NULL) + return NULL; + + s = heim_string_ref_create(str, free); + if (s == NULL) + free(str); + return s; +} + +/** + * Return the type ID of string objects + * + * @return type id of string objects + */ + +heim_tid_t +heim_string_get_type_id(void) +{ + return HEIM_TID_STRING; +} + +/** + * Get the string value of the content. + * + * @param string the string object to get the value from + * + * @return a utf8 string + */ + +const char * +heim_string_get_utf8(heim_string_t string) +{ + if (*(const char *)string == '\0') { + const char **strp; + + /* String ref */ + strp = _heim_get_isaextra(string, 1); + if (*strp != NULL) + return *strp; + } + return (const char *)string; +} + +/* + * + */ + +static void +init_string(void *ptr) +{ + heim_dict_t *dict = ptr; + *dict = heim_dict_create(101); + heim_assert(*dict != NULL, "__heim_string_constant"); +} + +heim_string_t +__heim_string_constant(const char *_str) +{ + static HEIMDAL_MUTEX mutex = HEIMDAL_MUTEX_INITIALIZER; + static heim_base_once_t once = HEIM_BASE_ONCE_INIT; + static heim_dict_t dict = NULL; + heim_string_t s, s2; + + heim_base_once_f(&once, &dict, init_string); + s = heim_string_create(_str); + + HEIMDAL_MUTEX_lock(&mutex); + s2 = heim_dict_get_value(dict, s); + if (s2) { + heim_release(s); + s = s2; + } else { + _heim_make_permanent(s); + heim_dict_set_value(dict, s, s); + } + HEIMDAL_MUTEX_unlock(&mutex); + + return s; +} diff --git a/third_party/heimdal/lib/base/test_base.c b/third_party/heimdal/lib/base/test_base.c new file mode 100644 index 0000000..cc875d7 --- /dev/null +++ b/third_party/heimdal/lib/base/test_base.c @@ -0,0 +1,1380 @@ +/* + * Copyright (c) 2010-2016 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This is a test of libheimbase functionality. If you make any changes + * to libheimbase or to this test you should run it under valgrind with + * the following options: + * + * -v --track-fds=yes --num-callers=30 --leak-check=full + * + * and make sure that there are no leaks that don't have + * __heim_string_constant() or heim_db_register() in their stack trace. + */ + +#include +#include +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#endif +#ifdef HAVE_IO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include "baselocl.h" +#include "heimbase-atomics.h" + +static void HEIM_CALLCONV +memory_free(heim_object_t obj) +{ +} + +static int +test_memory(void) +{ + void *ptr; + + ptr = heim_alloc(10, "memory", memory_free); + + heim_retain(ptr); + heim_release(ptr); + + heim_retain(ptr); + heim_release(ptr); + + heim_release(ptr); + + ptr = heim_alloc(10, "memory", NULL); + heim_release(ptr); + + return 0; +} + +static int +test_mutex(void) +{ + HEIMDAL_MUTEX m = HEIMDAL_MUTEX_INITIALIZER; + + HEIMDAL_MUTEX_lock(&m); + HEIMDAL_MUTEX_unlock(&m); + HEIMDAL_MUTEX_destroy(&m); + + HEIMDAL_MUTEX_init(&m); + HEIMDAL_MUTEX_lock(&m); + HEIMDAL_MUTEX_unlock(&m); + HEIMDAL_MUTEX_destroy(&m); + + return 0; +} + +static int +test_rwlock(void) +{ + HEIMDAL_RWLOCK l = HEIMDAL_RWLOCK_INITIALIZER; + + HEIMDAL_RWLOCK_rdlock(&l); + HEIMDAL_RWLOCK_unlock(&l); + HEIMDAL_RWLOCK_wrlock(&l); + HEIMDAL_RWLOCK_unlock(&l); + if (HEIMDAL_RWLOCK_trywrlock(&l) != 0) + err(1, "HEIMDAL_RWLOCK_trywrlock() failed with lock not held"); + HEIMDAL_RWLOCK_unlock(&l); + if (HEIMDAL_RWLOCK_tryrdlock(&l)) + err(1, "HEIMDAL_RWLOCK_tryrdlock() failed with lock not held"); + HEIMDAL_RWLOCK_unlock(&l); + HEIMDAL_RWLOCK_destroy(&l); + + HEIMDAL_RWLOCK_init(&l); + HEIMDAL_RWLOCK_rdlock(&l); + HEIMDAL_RWLOCK_unlock(&l); + HEIMDAL_RWLOCK_wrlock(&l); + HEIMDAL_RWLOCK_unlock(&l); + if (HEIMDAL_RWLOCK_trywrlock(&l)) + err(1, "HEIMDAL_RWLOCK_trywrlock() failed with lock not held"); + HEIMDAL_RWLOCK_unlock(&l); + if (HEIMDAL_RWLOCK_tryrdlock(&l)) + err(1, "HEIMDAL_RWLOCK_tryrdlock() failed with lock not held"); + HEIMDAL_RWLOCK_unlock(&l); + HEIMDAL_RWLOCK_destroy(&l); + + return 0; +} + +static int +test_dict(void) +{ + heim_dict_t dict; + heim_number_t a1 = heim_number_create(1); + heim_string_t a2 = heim_string_create("hejsan"); + heim_number_t a3 = heim_number_create(3); + heim_string_t a4 = heim_string_create("foosan"); + + dict = heim_dict_create(10); + + heim_dict_set_value(dict, a1, a2); + heim_dict_set_value(dict, a3, a4); + + heim_dict_delete_key(dict, a3); + heim_dict_delete_key(dict, a1); + + heim_release(a1); + heim_release(a2); + heim_release(a3); + heim_release(a4); + + heim_release(dict); + + return 0; +} + +static int +test_auto_release(void) +{ + heim_auto_release_t ar1, ar2; + heim_number_t n1; + heim_string_t s1; + + ar1 = heim_auto_release_create(); + + s1 = heim_string_create("hejsan"); + heim_auto_release(s1); + + n1 = heim_number_create(1); + heim_auto_release(n1); + + ar2 = heim_auto_release_create(); + + n1 = heim_number_create(1); + heim_auto_release(n1); + + heim_release(ar2); + heim_release(ar1); + + return 0; +} + +static int +test_string(void) +{ + heim_string_t s1, s2; + const char *string = "hejsan"; + + s1 = heim_string_create(string); + s2 = heim_string_create(string); + + if (heim_cmp(s1, s2) != 0) { + printf("the same string is not the same\n"); + exit(1); + } + + heim_release(s1); + heim_release(s2); + + return 0; +} + +static int +test_error(void) +{ + heim_error_t e; + heim_string_t s; + + e = heim_error_create(10, "foo: %s", "bar"); + heim_assert(heim_error_get_code(e) == 10, "error_code != 10"); + + s = heim_error_copy_string(e); + heim_assert(strcmp(heim_string_get_utf8(s), "foo: bar") == 0, "msg wrong"); + + heim_release(s); + heim_release(e); + + return 0; +} + +static int +test_json(void) +{ + static char *j[] = { + "{ \"k1\" : \"s1\", \"k2\" : \"s2\" }", + "{ \"k1\" : [\"s1\", \"s2\", \"s3\"], \"k2\" : \"s3\" }", + "{ \"k1\" : {\"k2\":\"s1\",\"k3\":\"s2\",\"k4\":\"s3\"}, \"k5\" : \"s4\" }", + ("[ \"v1\", \"v2\", [\"v3\",\"v4\",[\"v 5\",\" v 7 \"]], -123456789, " + "null, true, false, 123456789, \"\"]"), + " -1" + }; + char *s; + size_t i, k; + heim_object_t o, o2, o3; + heim_string_t k1 = heim_string_create("k1"); + + o = heim_json_create("\"string\"", 10, 0, NULL); + heim_assert(o != NULL, "string"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp("string", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* + * Test string escaping: + * + * - C-like must-escapes + * - ASCII control character must-escapes + * - surrogate pairs + * + * We test round-tripping. First we parse, then we serialize, then parse, + * then compare the second parse to the first for equality. + * + * We do compare serialized forms in spite of their not being canonical. + * That means that some changes to serialization can cause failures here. + */ + o = heim_json_create("\"" + "\\b\\f\\n\\r\\t" /* ASCII C-like escapes */ + "\x1e" /* ASCII control character w/o C-like escape */ + "\\u00e1" /* á */ + "\\u07ff" + "\\u0801" + "\\u8001" + "\\uD834\\udd1e" /* U+1D11E, as shown in RFC 7159 */ + "\"", 10, 0, NULL); + heim_assert(o != NULL, "string"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp( + "\b\f\n\r\t" + "\x1e" + "\xc3\xa1" + "\xdf\xbf" + "\xe0\xA0\x81" + "\xe8\x80\x81" + "\xf0\x9d\x84\x9e", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, + HEIM_JSON_F_STRICT | + HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL); + heim_assert(strcmp("\"\\b\\f\\n\\r\\t\\u001Eá߿ࠁ老\\uD834\\uDD1E\"", + heim_string_get_utf8(o2)) == 0, + "JSON encoding changed; please check that it is till valid"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + o = heim_json_create("\"" + "\\b\\f\\n\\r\\t" /* ASCII C-like escapes */ + "\x1e" /* ASCII control character w/o C-like escape */ + "\xc3\xa1" + "\xdf\xbf" + "\xe0\xa0\x81" + "\xE8\x80\x81" + "\\uD834\\udd1e" /* U+1D11E, as shown in RFC 7159 */ + "\"", 10, 0, NULL); + heim_assert(o != NULL, "string"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp( + "\b\f\n\r\t" + "\x1e" + "\xc3\xa1" + "\xdf\xbf" + "\xe0\xA0\x81" + "\xe8\x80\x81" + "\xf0\x9d\x84\x9e", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, + HEIM_JSON_F_STRICT | + HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL); + heim_assert(strcmp("\"\\b\\f\\n\\r\\t\\u001Eá߿ࠁ老\\uD834\\uDD1E\"", + heim_string_get_utf8(o2)) == 0, + "JSON encoding changed; please check that it is till valid"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* + * Test HEIM_JSON_F_ESCAPE_NON_ASCII. + * + * Also test that we get escaped non-ASCII because we're in a not-UTF-8 + * locale, since we setlocale(LC_ALL, "C"), so we should escape non-ASCII + * by default. + */ + o = heim_json_create("\"" + "\\b\\f\\n\\r\\t" /* ASCII C-like escapes */ + "\x1e" /* ASCII control character w/o C-like escape */ + "\xc3\xa1" + "\xdf\xbf" + "\xe0\xa0\x81" + "\xE8\x80\x81" + "\\uD834\\udd1e" /* U+1D11E, as shown in RFC 7159 */ + "\"", 10, 0, NULL); + heim_assert(o != NULL, "string"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp( + "\b\f\n\r\t" + "\x1e" + "\xc3\xa1" + "\xdf\xbf" + "\xe0\xA0\x81" + "\xe8\x80\x81" + "\xf0\x9d\x84\x9e", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, + HEIM_JSON_F_STRICT | + HEIM_JSON_F_ESCAPE_NON_ASCII, NULL); + heim_assert(strcmp("\"\\b\\f\\n\\r\\t\\u001E\\u00E1\\u07FF\\u0801\\u8001" + "\\uD834\\uDD1E\"", + heim_string_get_utf8(o2)) == 0, + "JSON encoding changed; please check that it is till valid"); + heim_release(o2); + o2 = heim_json_copy_serialize(o, HEIM_JSON_F_STRICT, NULL); + heim_assert(strcmp("\"\\b\\f\\n\\r\\t\\u001E\\u00E1\\u07FF\\u0801\\u8001" + "\\uD834\\uDD1E\"", + heim_string_get_utf8(o2)) == 0, + "JSON encoding changed; please check that it is till valid"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* Test rejection of unescaped ASCII control characters */ + o = heim_json_create("\"\b\\f\"", 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o == NULL, "strict parse accepted bad input"); + o = heim_json_create("\"\b\x1e\"", 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o == NULL, "strict parse accepted bad input"); + + o = heim_json_create("\"\b\\f\"", 10, 0, NULL); + heim_assert(o != NULL, "string"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp("\b\f", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, + HEIM_JSON_F_STRICT | + HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL); + heim_assert(strcmp("\"\\b\\f\"", heim_string_get_utf8(o2)) == 0, + "JSON encoding changed; please check that it is till valid"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* Test bogus backslash escape */ + o = heim_json_create("\"" + "\\ " + "\"", 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o == NULL, "malformed string accepted"); + o = heim_json_create("\"" + "\\ " + "\"", 10, 0, NULL); + heim_assert(o != NULL, "malformed string rejected (not strict)"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp(" ", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, + HEIM_JSON_F_STRICT | + HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL); + heim_assert(strcmp("\" \"", heim_string_get_utf8(o2)) == 0, + "JSON encoding changed; please check that it is till valid"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* Test truncated surrogate encoding (bottom code unit) */ + o = heim_json_create("\"" + "\xE8\x80\x81" + "\\uD834\\udd" + "\"", 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o == NULL, "malformed string accepted"); + o = heim_json_create("\"" + "\xE8\x80\x81" + "\\uD834\\udd" + "\"", 10, 0, NULL); + heim_assert(o != NULL, "malformed string rejected (not strict)"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp( + "\xe8\x80\x81" + "\\uD834\\udd", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, + HEIM_JSON_F_STRICT | + HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL); + heim_assert(strcmp("\"老\\\\uD834\\\\udd\"", + heim_string_get_utf8(o2)) == 0, + "JSON encoding changed; please check that it is till valid"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* Test truncated surrogate encodings (top code unit) */ + o = heim_json_create("\"" + "\xE8\x80\x81" + "\\uD83" + "\"", 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o == NULL, "malformed string accepted"); + o = heim_json_create("\"" + "\xE8\x80\x81" + "\\uD83" + "\"", 10, 0, NULL); + heim_assert(o != NULL, "malformed string rejected (not strict)"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp( + "\xe8\x80\x81" + "\\uD83", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, + HEIM_JSON_F_STRICT | + HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL); + heim_assert(strcmp("\"老\\\\uD83\"", + heim_string_get_utf8(o2)) == 0, + "JSON encoding changed; please check that it is till valid"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* + * Test handling of truncated UTF-8 multi-byte sequences. + */ + o = heim_json_create("\"" + "\xE8\x80" + "\"", 10, 0, NULL); + heim_assert(o != NULL, "malformed string rejected (not strict)"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp("\xe8\x80", + heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, + HEIM_JSON_F_STRICT | + HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL); + heim_assert(o2 == NULL, "malformed string serialized"); + o2 = heim_json_copy_serialize(o, HEIM_JSON_F_NO_ESCAPE_NON_ASCII, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o3 == NULL, "malformed string accepted (not strict)"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(strcmp("\xe8\x80", + heim_string_get_utf8(o3)) == 0, "wrong string"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* Test handling of unescaped / embedded newline */ + o = heim_json_create("\"\n\"", 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o == NULL, "malformed string accepted (strict)"); + o = heim_json_create("\"\n\"", 10, 0, NULL); + heim_assert(o != NULL, "malformed string rejected (not strict)"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp("\n", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, HEIM_JSON_F_STRICT, NULL); + heim_assert(o2 != NULL, "string not serialized"); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o3 != NULL, "string not accepted"); + heim_assert(strcmp("\n", heim_string_get_utf8(o3)) == 0, "wrong string"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* Test handling of embedded NULs (must decode as data, not string) */ + o = heim_json_create("\"\\u0000\"", 10, HEIM_JSON_F_STRICT, NULL); + heim_assert(o != NULL, "string with NULs rejected"); + heim_assert(heim_get_tid(o) == heim_data_get_type_id(), "data-tid"); + heim_assert(heim_data_get_length(o) == 1, "wrong data length"); + heim_assert(((const char *)heim_data_get_ptr(o))[0] == '\0', + "wrong data NUL"); + o2 = heim_json_copy_serialize(o, 0, NULL); + heim_assert(o2 != NULL, "data not serialized"); + heim_release(o2); + heim_release(o); + + /* + * Note that the trailing ']' is not part of the JSON text (which is just a + * string). + */ + o = heim_json_create(" \"foo\\\"bar\" ]", 10, 0, NULL); + heim_assert(o != NULL, "string"); + heim_assert(heim_get_tid(o) == heim_string_get_type_id(), "string-tid"); + heim_assert(strcmp("foo\"bar", heim_string_get_utf8(o)) == 0, "wrong string"); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + o = heim_json_create(" { \"key\" : \"value\" }", 10, 0, NULL); + heim_assert(o != NULL, "dict"); + heim_assert(heim_get_tid(o) == heim_dict_get_type_id(), "dict-tid"); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + /* + * heim_json_eq() can't handle dicts with dicts as keys, so we don't check + * for round-tripping here + */ + o = heim_json_create("{ { \"k1\" : \"s1\", \"k2\" : \"s2\" } : \"s3\", " + "{ \"k3\" : \"s4\" } : -1 }", 10, 0, NULL); + heim_assert(o != NULL, "dict"); + heim_assert(heim_get_tid(o) == heim_dict_get_type_id(), "dict-tid"); + heim_release(o); + + o = heim_json_create("{ { \"k1\" : \"s1\", \"k2\" : \"s2\" } : \"s3\", " + "{ \"k3\" : \"s4\" } : -1 }", 10, + HEIM_JSON_F_STRICT_DICT, NULL); + heim_assert(o == NULL, "dict"); + + o = heim_json_create(" { \"k1\" : \"s1\", \"k2\" : \"s2\" }", 10, 0, NULL); + heim_assert(o != NULL, "dict"); + heim_assert(heim_get_tid(o) == heim_dict_get_type_id(), "dict-tid"); + o2 = heim_dict_copy_value(o, k1); + heim_assert(heim_get_tid(o2) == heim_string_get_type_id(), "string-tid"); + heim_release(o2); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + o = heim_json_create(" { \"k1\" : { \"k2\" : \"s2\" } }", 10, 0, NULL); + heim_assert(o != NULL, "dict"); + heim_assert(heim_get_tid(o) == heim_dict_get_type_id(), "dict-tid"); + o2 = heim_dict_copy_value(o, k1); + heim_assert(heim_get_tid(o2) == heim_dict_get_type_id(), "dict-tid"); + heim_release(o2); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + o = heim_json_create("{ \"k1\" : 1 }", 10, 0, NULL); + heim_assert(o != NULL, "array"); + heim_assert(heim_get_tid(o) == heim_dict_get_type_id(), "dict-tid"); + o2 = heim_dict_copy_value(o, k1); + heim_assert(heim_get_tid(o2) == heim_number_get_type_id(), "number-tid"); + heim_release(o2); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + o = heim_json_create("-10", 10, 0, NULL); + heim_assert(o != NULL, "number"); + heim_assert(heim_get_tid(o) == heim_number_get_type_id(), "number-tid"); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + o = heim_json_create("99", 10, 0, NULL); + heim_assert(o != NULL, "number"); + heim_assert(heim_get_tid(o) == heim_number_get_type_id(), "number-tid"); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + o = heim_json_create(" [ 1 ]", 10, 0, NULL); + heim_assert(o != NULL, "array"); + heim_assert(heim_get_tid(o) == heim_array_get_type_id(), "array-tid"); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + o = heim_json_create(" [ -1 ]", 10, 0, NULL); + heim_assert(o != NULL, "array"); + heim_assert(heim_get_tid(o) == heim_array_get_type_id(), "array-tid"); + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + + for (i = 0; i < (sizeof (j) / sizeof (j[0])); i++) { + o = heim_json_create(j[i], 10, 0, NULL); + if (o == NULL) { + fprintf(stderr, "Failed to parse this JSON: %s\n", j[i]); + return 1; + } + o2 = heim_json_copy_serialize(o, 0, NULL); + o3 = heim_json_create(heim_string_get_utf8(o2), 10, 0, NULL); + heim_assert(heim_json_eq(o, o3), "JSON text did not round-trip"); + heim_release(o3); + heim_release(o2); + heim_release(o); + /* Simple fuzz test */ + for (k = strlen(j[i]) - 1; k > 0; k--) { + o = heim_json_create_with_bytes(j[i], k, 10, 0, NULL); + if (o != NULL) { + fprintf(stderr, "Invalid JSON parsed: %.*s\n", (int)k, j[i]); + return EINVAL; + } + } + /* Again, but this time make it so valgrind can find invalid accesses */ + for (k = strlen(j[i]) - 1; k > 0; k--) { + s = strndup(j[i], k); + if (s == NULL) + return ENOMEM; + o = heim_json_create(s, 10, 0, NULL); + free(s); + if (o != NULL) { + fprintf(stderr, "Invalid JSON parsed: %s\n", j[i]); + return EINVAL; + } + } + /* Again, but with no NUL termination */ + for (k = strlen(j[i]) - 1; k > 0; k--) { + s = malloc(k); + if (s == NULL) + return ENOMEM; + memcpy(s, j[i], k); + o = heim_json_create_with_bytes(s, k, 10, 0, NULL); + free(s); + if (o != NULL) { + fprintf(stderr, "Invalid JSON parsed: %s\n", j[i]); + return EINVAL; + } + } + } + + heim_release(k1); + + return 0; +} + +static int +test_path(void) +{ + heim_dict_t dict = heim_dict_create(11); + heim_string_t p1 = heim_string_create("abc"); + heim_string_t p2a = heim_string_create("def"); + heim_string_t p2b = heim_string_create("DEF"); + heim_number_t p3 = heim_number_create(0); + heim_string_t p4a = heim_string_create("ghi"); + heim_string_t p4b = heim_string_create("GHI"); + heim_array_t a = heim_array_create(); + heim_number_t l1 = heim_number_create(42); + heim_number_t l2 = heim_number_create(813); + heim_number_t l3 = heim_number_create(1234); + heim_string_t k1 = heim_string_create("k1"); + heim_string_t k2 = heim_string_create("k2"); + heim_string_t k3 = heim_string_create("k3"); + heim_string_t k2_1 = heim_string_create("k2-1"); + heim_string_t k2_2 = heim_string_create("k2-2"); + heim_string_t k2_3 = heim_string_create("k2-3"); + heim_string_t k2_4 = heim_string_create("k2-4"); + heim_string_t k2_5 = heim_string_create("k2-5"); + heim_string_t k2_5_1 = heim_string_create("k2-5-1"); + heim_object_t o; + heim_object_t neg_num; + int ret; + + if (!dict || !p1 || !p2a || !p2b || !p4a || !p4b) + return ENOMEM; + + ret = heim_path_create(dict, 11, a, NULL, p1, p2a, NULL); + heim_release(a); + if (ret) + return ret; + ret = heim_path_create(dict, 11, l3, NULL, p1, p2b, NULL); + if (ret) + return ret; + o = heim_path_get(dict, NULL, p1, p2b, NULL); + if (o != l3) + return 1; + ret = heim_path_create(dict, 11, NULL, NULL, p1, p2a, p3, NULL); + if (ret) + return ret; + ret = heim_path_create(dict, 11, l1, NULL, p1, p2a, p3, p4a, NULL); + if (ret) + return ret; + ret = heim_path_create(dict, 11, l2, NULL, p1, p2a, p3, p4b, NULL); + if (ret) + return ret; + + o = heim_path_get(dict, NULL, p1, p2a, p3, p4a, NULL); + if (o != l1) + return 1; + o = heim_path_get(dict, NULL, p1, p2a, p3, p4b, NULL); + if (o != l2) + return 1; + + heim_release(dict); + + /* Test that JSON parsing works right by using heim_path_get() */ + dict = heim_json_create("{\"k1\":1," + "\"k2\":{\"k2-1\":21," + "\"k2-2\":null," + "\"k2-3\":true," + "\"k2-4\":false," + "\"k2-5\":[1,2,3,{\"k2-5-1\":-1},-2]}," + "\"k3\":[true,false,0,42]}", 10, 0, NULL); + heim_assert(dict != NULL, "dict"); + o = heim_path_get(dict, NULL, k1, NULL); + if (heim_cmp(o, heim_number_create(1))) return 1; + o = heim_path_get(dict, NULL, k2, NULL); + if (heim_get_tid(o) != heim_dict_get_type_id()) return 1; + o = heim_path_get(dict, NULL, k2, k2_1, NULL); + if (heim_cmp(o, heim_number_create(21))) return 1; + o = heim_path_get(dict, NULL, k2, k2_2, NULL); + if (heim_cmp(o, heim_null_create())) return 1; + o = heim_path_get(dict, NULL, k2, k2_3, NULL); + if (heim_cmp(o, heim_bool_create(1))) return 1; + o = heim_path_get(dict, NULL, k2, k2_4, NULL); + if (heim_cmp(o, heim_bool_create(0))) return 1; + o = heim_path_get(dict, NULL, k2, k2_5, NULL); + if (heim_get_tid(o) != heim_array_get_type_id()) return 1; + o = heim_path_get(dict, NULL, k2, k2_5, heim_number_create(0), NULL); + if (heim_cmp(o, heim_number_create(1))) return 1; + o = heim_path_get(dict, NULL, k2, k2_5, heim_number_create(1), NULL); + if (heim_cmp(o, heim_number_create(2))) return 1; + o = heim_path_get(dict, NULL, k2, k2_5, heim_number_create(3), k2_5_1, NULL); + if (heim_cmp(o, neg_num = heim_number_create(-1))) return 1; + heim_release(neg_num); + o = heim_path_get(dict, NULL, k2, k2_5, heim_number_create(4), NULL); + if (heim_cmp(o, neg_num = heim_number_create(-2))) return 1; + heim_release(neg_num); + o = heim_path_get(dict, NULL, k3, heim_number_create(3), NULL); + if (heim_cmp(o, heim_number_create(42))) return 1; + + heim_release(dict); + heim_release(p1); + heim_release(p2a); + heim_release(p2b); + heim_release(p4a); + heim_release(p4b); + heim_release(k1); + heim_release(k2); + heim_release(k3); + heim_release(k2_1); + heim_release(k2_2); + heim_release(k2_3); + heim_release(k2_4); + heim_release(k2_5); + heim_release(k2_5_1); + + return 0; +} + +typedef struct dict_db { + heim_dict_t dict; + int locked; +} *dict_db_t; + +static int +dict_db_open(void *plug, const char *dbtype, const char *dbname, + heim_dict_t options, void **db, heim_error_t *error) +{ + dict_db_t dictdb; + heim_dict_t contents = NULL; + + if (error) + *error = NULL; + if (dbtype && *dbtype && strcmp(dbtype, "dictdb") != 0) + return EINVAL; + if (dbname && *dbname && strcmp(dbname, "MEMORY") != 0) + return EINVAL; + dictdb = heim_alloc(sizeof (*dictdb), "dict_db", NULL); + if (dictdb == NULL) + return ENOMEM; + + if (contents != NULL) + dictdb->dict = contents; + else { + dictdb->dict = heim_dict_create(29); + if (dictdb->dict == NULL) { + heim_release(dictdb); + return ENOMEM; + } + } + + *db = dictdb; + return 0; +} + +static int +dict_db_close(void *db, heim_error_t *error) +{ + dict_db_t dictdb = db; + + if (error) + *error = NULL; + heim_release(dictdb->dict); + heim_release(dictdb); + return 0; +} + +static int +dict_db_lock(void *db, int read_only, heim_error_t *error) +{ + dict_db_t dictdb = db; + + if (error) + *error = NULL; + if (dictdb->locked) + return EWOULDBLOCK; + dictdb->locked = 1; + return 0; +} + +static int +dict_db_unlock(void *db, heim_error_t *error) +{ + dict_db_t dictdb = db; + + if (error) + *error = NULL; + dictdb->locked = 0; + return 0; +} + +static heim_data_t +dict_db_copy_value(void *db, heim_string_t table, heim_data_t key, + heim_error_t *error) +{ + dict_db_t dictdb = db; + + if (error) + *error = NULL; + + return heim_retain(heim_path_get(dictdb->dict, error, table, key, NULL)); +} + +static int +dict_db_set_value(void *db, heim_string_t table, + heim_data_t key, heim_data_t value, heim_error_t *error) +{ + dict_db_t dictdb = db; + + if (error) + *error = NULL; + + if (table == NULL) + table = HSTR(""); + + return heim_path_create(dictdb->dict, 29, value, error, table, key, NULL); +} + +static int +dict_db_del_key(void *db, heim_string_t table, heim_data_t key, + heim_error_t *error) +{ + dict_db_t dictdb = db; + + if (error) + *error = NULL; + + if (table == NULL) + table = HSTR(""); + + heim_path_delete(dictdb->dict, error, table, key, NULL); + return 0; +} + +struct dict_db_iter_ctx { + heim_db_iterator_f_t iter_f; + void *iter_ctx; +}; + +static void dict_db_iter_f(heim_object_t key, heim_object_t value, void *arg) +{ + struct dict_db_iter_ctx *ctx = arg; + + ctx->iter_f((heim_object_t)key, (heim_object_t)value, ctx->iter_ctx); +} + +static void +dict_db_iter(void *db, heim_string_t table, void *iter_data, + heim_db_iterator_f_t iter_f, heim_error_t *error) +{ + dict_db_t dictdb = db; + struct dict_db_iter_ctx ctx; + heim_dict_t table_dict; + + if (error) + *error = NULL; + + if (table == NULL) + table = HSTR(""); + + table_dict = heim_dict_copy_value(dictdb->dict, table); + if (table_dict == NULL) + return; + + ctx.iter_ctx = iter_data; + ctx.iter_f = iter_f; + + heim_dict_iterate_f(table_dict, &ctx, dict_db_iter_f); + heim_release(table_dict); +} + +static void +test_db_iter(heim_data_t k, heim_data_t v, void *arg) +{ + int *ret = arg; + const void *kptr, *vptr; + size_t klen, vlen; + + heim_assert(heim_get_tid(k) == heim_data_get_type_id(), "..."); + + kptr = heim_data_get_ptr(k); + klen = heim_data_get_length(k); + vptr = heim_data_get_ptr(v); + vlen = heim_data_get_length(v); + + if (klen == strlen("msg") && strncmp(kptr, "msg", strlen("msg")) == 0 && + vlen == strlen("abc") && strncmp(vptr, "abc", strlen("abc")) == 0) + *ret &= ~(1); + else if (klen == strlen("msg2") && + strncmp(kptr, "msg2", strlen("msg2")) == 0 && + vlen == strlen("FooBar") && + strncmp(vptr, "FooBar", strlen("FooBar")) == 0) + *ret &= ~(2); + else + *ret |= 4; +} + +static struct heim_db_type dbt = { + 1, dict_db_open, NULL, dict_db_close, + dict_db_lock, dict_db_unlock, NULL, NULL, NULL, NULL, + dict_db_copy_value, dict_db_set_value, + dict_db_del_key, dict_db_iter +}; + +static int +test_db(const char *dbtype, const char *dbname) +{ + heim_data_t k1, k2, v, v1, v2, v3; + heim_db_t db; + int ret; + + if (dbtype == NULL) { + ret = heim_db_register("dictdb", NULL, &dbt); + heim_assert(!ret, "..."); + db = heim_db_create("dictdb", "foo", NULL, NULL); + heim_assert(!db, "..."); + db = heim_db_create("foobar", "MEMORY", NULL, NULL); + heim_assert(!db, "..."); + db = heim_db_create("dictdb", "MEMORY", NULL, NULL); + heim_assert(db, "..."); + } else { + heim_dict_t options; + + options = heim_dict_create(11); + if (options == NULL) return ENOMEM; + if (heim_dict_set_value(options, HSTR("journal-filename"), + HSTR("json-journal"))) + return ENOMEM; + if (heim_dict_set_value(options, HSTR("create"), heim_null_create())) + return ENOMEM; + if (heim_dict_set_value(options, HSTR("truncate"), heim_null_create())) + return ENOMEM; + db = heim_db_create(dbtype, dbname, options, NULL); + heim_assert(db, "..."); + heim_release(options); + } + + k1 = heim_data_create("msg", strlen("msg")); + k2 = heim_data_create("msg2", strlen("msg2")); + v1 = heim_data_create("Hello world!", strlen("Hello world!")); + v2 = heim_data_create("FooBar", strlen("FooBar")); + v3 = heim_data_create("abc", strlen("abc")); + + ret = heim_db_set_value(db, NULL, k1, v1, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k1, NULL); + heim_assert(v && !heim_cmp(v, v1), "..."); + heim_release(v); + + ret = heim_db_set_value(db, NULL, k2, v2, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k2, NULL); + heim_assert(v && !heim_cmp(v, v2), "..."); + heim_release(v); + + ret = heim_db_set_value(db, NULL, k1, v3, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k1, NULL); + heim_assert(v && !heim_cmp(v, v3), "..."); + heim_release(v); + + ret = 3; + heim_db_iterate_f(db, NULL, &ret, test_db_iter, NULL); + heim_assert(!ret, "..."); + + ret = heim_db_begin(db, 0, NULL); + heim_assert(!ret, "..."); + + ret = heim_db_commit(db, NULL); + heim_assert(!ret, "..."); + + ret = heim_db_begin(db, 0, NULL); + heim_assert(!ret, "..."); + + ret = heim_db_rollback(db, NULL); + heim_assert(!ret, "..."); + + ret = heim_db_begin(db, 0, NULL); + heim_assert(!ret, "..."); + + ret = heim_db_set_value(db, NULL, k1, v1, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k1, NULL); + heim_assert(v && !heim_cmp(v, v1), "..."); + heim_release(v); + + ret = heim_db_rollback(db, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k1, NULL); + heim_assert(v && !heim_cmp(v, v3), "..."); + heim_release(v); + + ret = heim_db_begin(db, 0, NULL); + heim_assert(!ret, "..."); + + ret = heim_db_set_value(db, NULL, k1, v1, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k1, NULL); + heim_assert(v && !heim_cmp(v, v1), "..."); + heim_release(v); + + ret = heim_db_commit(db, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k1, NULL); + heim_assert(v && !heim_cmp(v, v1), "..."); + heim_release(v); + + ret = heim_db_begin(db, 0, NULL); + heim_assert(!ret, "..."); + + ret = heim_db_delete_key(db, NULL, k1, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k1, NULL); + heim_assert(v == NULL, "..."); + heim_release(v); + + ret = heim_db_rollback(db, NULL); + heim_assert(!ret, "..."); + + v = heim_db_copy_value(db, NULL, k1, NULL); + heim_assert(v && !heim_cmp(v, v1), "..."); + heim_release(v); + + if (dbtype != NULL) { + heim_data_t k3 = heim_data_create("value-is-a-dict", strlen("value-is-a-dict")); + heim_dict_t vdict = heim_dict_create(11); + heim_db_t db2; + + heim_assert(k3 && vdict, "..."); + ret = heim_dict_set_value(vdict, HSTR("vdict-k1"), heim_number_create(11)); + heim_assert(!ret, "..."); + ret = heim_dict_set_value(vdict, HSTR("vdict-k2"), heim_null_create()); + heim_assert(!ret, "..."); + ret = heim_dict_set_value(vdict, HSTR("vdict-k3"), HSTR("a value")); + heim_assert(!ret, "..."); + ret = heim_db_set_value(db, NULL, k3, (heim_data_t)vdict, NULL); + heim_assert(!ret, "..."); + + heim_release(vdict); + + db2 = heim_db_create(dbtype, dbname, NULL, NULL); + heim_assert(db2, "..."); + + vdict = (heim_dict_t)heim_db_copy_value(db2, NULL, k3, NULL); + heim_release(db2); + heim_release(k3); + heim_assert(vdict, "..."); + heim_assert(heim_get_tid(vdict) == heim_dict_get_type_id(), "..."); + + v = heim_dict_copy_value(vdict, HSTR("vdict-k1")); + heim_assert(v && !heim_cmp(v, heim_number_create(11)), "..."); + heim_release(v); + + v = heim_dict_copy_value(vdict, HSTR("vdict-k2")); + heim_assert(v && !heim_cmp(v, heim_null_create()), "..."); + heim_release(v); + + v = heim_dict_copy_value(vdict, HSTR("vdict-k3")); + heim_assert(v && !heim_cmp(v, HSTR("a value")), "..."); + heim_release(v); + + heim_release(vdict); + } + + heim_release(db); + heim_release(k1); + heim_release(k2); + heim_release(v1); + heim_release(v2); + heim_release(v3); + + return 0; +} + +struct test_array_iter_ctx { + char buf[256]; +}; + +static void test_array_iter(heim_object_t elt, void *arg, int *stop) +{ + struct test_array_iter_ctx *iter_ctx = arg; + + strcat(iter_ctx->buf, heim_string_get_utf8((heim_string_t)elt)); +} + +static int +test_array() +{ + struct test_array_iter_ctx iter_ctx; + heim_string_t s1 = heim_string_create("abc"); + heim_string_t s2 = heim_string_create("def"); + heim_string_t s3 = heim_string_create("ghi"); + heim_string_t s4 = heim_string_create("jkl"); + heim_string_t s5 = heim_string_create("mno"); + heim_string_t s6 = heim_string_create("pqr"); + heim_array_t a = heim_array_create(); + + if (!s1 || !s2 || !s3 || !s4 || !s5 || !s6 || !a) + return ENOMEM; + + heim_array_append_value(a, s4); + heim_array_append_value(a, s5); + heim_array_insert_value(a, 0, s3); + heim_array_insert_value(a, 0, s2); + heim_array_append_value(a, s6); + heim_array_insert_value(a, 0, s1); + + iter_ctx.buf[0] = '\0'; + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "abcdefghijklmnopqr") != 0) + return 1; + + iter_ctx.buf[0] = '\0'; + heim_array_delete_value(a, 2); + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "abcdefjklmnopqr") != 0) + return 1; + + iter_ctx.buf[0] = '\0'; + heim_array_delete_value(a, 2); + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "abcdefmnopqr") != 0) + return 1; + + iter_ctx.buf[0] = '\0'; + heim_array_delete_value(a, 0); + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "defmnopqr") != 0) + return 1; + + iter_ctx.buf[0] = '\0'; + heim_array_delete_value(a, 2); + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "defmno") != 0) + return 1; + + heim_array_insert_value(a, 0, s1); + iter_ctx.buf[0] = '\0'; + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "abcdefmno") != 0) + return 1; + + heim_array_insert_value(a, 0, s2); + iter_ctx.buf[0] = '\0'; + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "defabcdefmno") != 0) + return 1; + + heim_array_append_value(a, s3); + iter_ctx.buf[0] = '\0'; + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "defabcdefmnoghi") != 0) + return 1; + + heim_array_append_value(a, s6); + iter_ctx.buf[0] = '\0'; + heim_array_iterate_f(a, &iter_ctx, test_array_iter); + if (strcmp(iter_ctx.buf, "defabcdefmnoghipqr") != 0) + return 1; + + heim_release(s1); + heim_release(s2); + heim_release(s3); + heim_release(s4); + heim_release(s5); + heim_release(s6); + heim_release(a); + + return 0; +} + +/* This function tests only that heimbase-atomics.h compiles */ +static int +test_atomics(void) +{ + heim_base_atomic(void *) tptr; + heim_base_atomic(uint32_t) tu32; + heim_base_atomic(uint64_t) tu64; + + heim_base_atomic_init(&tptr, NULL); + heim_base_atomic_init(&tu32, 0); + heim_base_atomic_init(&tu64, 0); + + if (heim_base_atomic_load(&tptr)) + return 1; + if (heim_base_atomic_load(&tu32)) + return 1; + if (heim_base_atomic_load(&tu64)) + return 1; + + heim_base_atomic_store(&tptr, &tptr); + heim_base_atomic_store(&tu32, 1); + heim_base_atomic_store(&tu64, 1); + + if (heim_base_atomic_load(&tptr) != &tptr) + return 1; + if (heim_base_atomic_load(&tu32) != 1) + return 1; + if (heim_base_atomic_load(&tu64) != 1) + return 1; + + if (heim_base_atomic_inc_32(&tu32) != 2 || + heim_base_atomic_load(&tu32) != 2) + return 1; + if (heim_base_atomic_inc_64(&tu64) != 2 || + heim_base_atomic_load(&tu64) != 2) + return 1; + + if (heim_base_atomic_dec_32(&tu32) != 1 || + heim_base_atomic_load(&tu32) != 1) + return 1; + if (heim_base_atomic_dec_64(&tu64) != 1 || + heim_base_atomic_load(&tu64) != 1) + return 1; + + heim_base_exchange_pointer(&tptr, (void *)&tu32); + if (heim_base_atomic_load(&tptr) != &tu32) + return 1; + heim_base_exchange_32(&tu32, 32); + if (heim_base_atomic_load(&tu32) != 32) + return 1; + heim_base_exchange_64(&tu64, 64); + if (heim_base_atomic_load(&tu64) != 64) + return 1; + + if (heim_base_cas_pointer(&tptr, (void *)&tu32, (void *)&tu64) != &tu32) + return 1; + if (heim_base_cas_pointer(&tptr, (void *)&tu32, (void *)&tptr) != &tu64) + return 1; + if (heim_base_atomic_load(&tptr) != (void *)&tu64) + return 1; + + if (heim_base_cas_32(&tu32, 32, 4) != 32) + return 1; + if (heim_base_cas_32(&tu32, 32, 4) != 4) + return 1; + if (heim_base_atomic_load(&tu32) != 4) + return 1; + + if (heim_base_cas_64(&tu64, 64, 4) != 64) + return 1; + if (heim_base_cas_64(&tu64, 64, 4) != 4) + return 1; + if (heim_base_atomic_load(&tu64) != 4) + return 1; + + return 0; +} + +int +main(int argc, char **argv) +{ + int res = 0; + +#ifndef WIN32 + setlocale(LC_ALL, "C"); + heim_assert(!heim_locale_is_utf8(), "setlocale(LC_ALL, \"C\") failed?"); +#endif + + res |= test_memory(); + res |= test_mutex(); + res |= test_rwlock(); + res |= test_dict(); + res |= test_auto_release(); + res |= test_string(); + res |= test_error(); + res |= test_json(); + res |= test_path(); + res |= test_db(NULL, NULL); + res |= test_db("json", argc > 1 ? argv[1] : "test_db.json"); + res |= test_array(); + res |= test_atomics(); + + return res ? 1 : 0; +} diff --git a/third_party/heimdal/lib/base/version-script.map b/third_party/heimdal/lib/base/version-script.map new file mode 100644 index 0000000..1ba8899 --- /dev/null +++ b/third_party/heimdal/lib/base/version-script.map @@ -0,0 +1,211 @@ +HEIMDAL_BASE_1.0 { + global: + _bsearch_file; + _bsearch_file_close; + _bsearch_file_info; + _bsearch_file_open; + _bsearch_text; + DllMain; + et_heim_error_table; + heim_abort; + heim_abortv; + heim_add_debug_dest; + heim_add_et_list; + heim_addlog_dest; + heim_addlog_func; + heim_add_warn_dest; + heim_alloc; + _heim_alloc_object; + heim_array_append_value; + heim_array_copy_value; + heim_array_create; + heim_array_delete_value; + heim_array_filter_f; + heim_array_get_length; + heim_array_get_type_id; + heim_array_get_value; + heim_array_insert_value; + heim_array_iterate_f; + heim_array_iterate_reverse_f; + heim_array_set_value; + heim_audit_addkv; + heim_audit_addkv_number; + heim_audit_addkv_object; + heim_audit_addkv_timediff; + heim_audit_setkv_bool; + heim_audit_setkv_number; + heim_audit_setkv_object; + heim_audit_addreason; + heim_audit_getkv; + heim_audit_trail; + heim_audit_vaddkv; + heim_audit_vaddreason; + heim_auto_release; + heim_auto_release_create; + heim_auto_release_drain; + heim_base_once_f; + heim_base_mutex; + heim_bool_create; + heim_bool_val; + heim_clear_error_message; + heim_closelog; + heim_cmp; + heim_config_copy; + heim_config_file_free; + heim_config_free_strings; + heim_config_get; + heim_config_get_bool; + heim_config_get_bool_default; + heim_config_get_entry; + heim_config_get_int; + heim_config_get_int_default; + heim_config_get_list; + heim_config_get_next; + heim_config_get_string; + heim_config_get_string_default; + heim_config_get_strings; + heim_config_get_time; + heim_config_get_time_default; + heim_config_parse_dir_multi; + heim_config_parse_file; + heim_config_parse_file_multi; + heim_config_parse_string_multi; + heim_config_vget; + heim_config_vget_bool; + heim_config_vget_bool_default; + heim_config_vget_int; + heim_config_vget_int_default; + heim_config_vget_list; + heim_config_vget_next; + heim_config_vget_string; + heim_config_vget_string_default; + heim_config_vget_strings; + heim_config_vget_time; + heim_config_vget_time_default; + heim_context_free; + heim_context_get_homedir_access; + heim_context_get_log_utc; + heim_context_get_time_fmt; + heim_context_init; + heim_context_set_homedir_access; + heim_context_set_log_utc; + heim_context_set_time_fmt; + _heim_create_type; + heim_data_create; + heim_data_get_data; + heim_data_get_length; + heim_data_get_ptr; + heim_data_get_type_id; + heim_data_ref_create; + heim_data_ref_get_type_id; + heim_db_begin; + heim_db_clone; + heim_db_commit; + heim_db_copy_value; + heim_db_create; + heim_db_delete_key; + heim_db_get_type_id; + _heim_db_get_value; + heim_db_iterate_f; + heim_db_register; + heim_db_rollback; + heim_db_set_value; + heim_debug; + heim_description; + heim_dict_copy_value; + heim_dict_create; + heim_dict_delete_key; + heim_dict_get_type_id; + heim_dict_get_value; + heim_dict_iterate_f; + heim_dict_set_value; + heim_enomem; + heim_error_append; + heim_error_copy_string; + heim_error_create; + heim_error_create_enomem; + heim_error_create_opt; + heim_error_createv; + heim_error_get_code; + heim_expand_path_tokens; + heim_expand_path_tokensv; + heim_free_config_files; + heim_free_error_message; + heim_get_debug_dest; + heim_get_default_config_files; + heim_get_error_message; + heim_get_error_string; + heim_get_hash; + _heim_get_isa; + _heim_get_isaextra; + heim_get_log_dest; + heim_get_tid; + heim_get_warn_dest; + heim_have_debug; + heim_have_error_string; + heim_initlog; + heim_json_copy_serialize; + heim_json_create; + heim_json_create_with_bytes; + heim_json_eq; + heim_load_plugins; + heim_locale_is_utf8; + heim_log; + heim_log_msg; + _heim_make_permanent; + heim_null_create; + heim_number_create; + heim_number_get_int; + heim_number_get_long; + heim_number_get_type_id; + heim_openlog; + heim_path_copy; + heim_path_create; + heim_path_delete; + heim_path_get; + heim_path_vcopy; + heim_path_vcreate; + heim_path_vdelete; + heim_path_vget; + heim_plugin_register; + heim_plugin_run_f; + heim_prepend_config_files; + heim_prepend_config_files_default; + heim_prepend_error_message; + heim_release; + heim_retain; + heim_set_config_files; + heim_set_debug_dest; + heim_set_error_message; + heim_set_log_dest; + heim_set_warn_dest; + heim_show; + heim_sorted_text_file_dbtype; + __heim_string_constant; + heim_string_create; + heim_string_create_with_bytes; + heim_string_create_with_format; + heim_string_get_type_id; + heim_string_get_utf8; + heim_string_ref_create; + _heim_type_get_tid; + heim_unload_plugins; + heim_vdebug; + heim_vlog; + heim_vlog_msg; + heim_vprepend_error_message; + heim_vset_error_message; + heim_vwarn; + heim_vwarnx; + heim_w32_delete_key; + heim_w32_getspecific; + heim_w32_key_create; + heim_w32_service_thread_detach; + heim_w32_setspecific; + heim_warn; + heim_warnx; + initialize_heim_error_table; + initialize_heim_error_table_r; + local: + *; +}; diff --git a/third_party/heimdal/lib/base/warn.c b/third_party/heimdal/lib/base/warn.c new file mode 100644 index 0000000..3dfed48 --- /dev/null +++ b/third_party/heimdal/lib/base/warn.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 1997 - 2020 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(_MSC_VER) +# pragma warning(disable: 4646) +# pragma warning(disable: 4716) +#endif + +#include "baselocl.h" +#include + +static heim_error_code _warnerr(heim_context context, int do_errtext, + heim_error_code code, int level, const char *fmt, va_list ap) + __attribute__ ((__format__ (__printf__, 5, 0))); + +static heim_error_code +_warnerr(heim_context context, int do_errtext, + heim_error_code code, int level, const char *fmt, va_list ap) +{ + char xfmt[7] = ""; + const char *args[2], **arg; + char *msg = NULL; + const char *err_str = NULL; + heim_error_code ret; + + args[0] = args[1] = NULL; + arg = args; + if(fmt){ + strlcat(xfmt, "%s", sizeof(xfmt)); + if(do_errtext) + strlcat(xfmt, ": ", sizeof(xfmt)); + ret = vasprintf(&msg, fmt, ap); + if(ret < 0 || msg == NULL) + return ENOMEM; + *arg++ = msg; + } + if (do_errtext) { + strlcat(xfmt, "%s", sizeof(xfmt)); + + err_str = heim_get_error_message(context, code); + if (err_str != NULL) { + *arg = err_str; + } else { + *arg= ""; + } + } + + if (context && heim_get_warn_dest(context)) + heim_log(context, heim_get_warn_dest(context), level, xfmt, args[0], + args[1]); + else + warnx(xfmt, args[0], args[1]); + free(msg); + heim_free_error_message(context, err_str); + return 0; +} + +#define FUNC(ETEXT, CODE, LEVEL) \ + heim_error_code ret; \ + va_list ap; \ + va_start(ap, fmt); \ + ret = _warnerr(context, ETEXT, CODE, LEVEL, fmt, ap); \ + va_end(ap); + +#undef __attribute__ +#define __attribute__(X) + +/** + * Log a warning to the log, default stderr, include the error from + * the last failure. + * + * @param context A Kerberos 5 context. + * @param code error code of the last error + * @param fmt message to print + * @param ap arguments + * + * @ingroup heim_error + */ + +heim_error_code +heim_vwarn(heim_context context, heim_error_code code, + const char *fmt, va_list ap) + __attribute__ ((__format__ (__printf__, 3, 0))) +{ + return _warnerr(context, 1, code, 1, fmt, ap); +} + +/** + * Log a warning to the log, default stderr, include the error from + * the last failure. + * + * @param context A Kerberos 5 context. + * @param code error code of the last error + * @param fmt message to print + * + * @ingroup heim_error + */ + +heim_error_code +heim_warn(heim_context context, heim_error_code code, const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 3, 4))) +{ + FUNC(1, code, 1); + return ret; +} + +/** + * Log a warning to the log, default stderr. + * + * @param context A Kerberos 5 context. + * @param fmt message to print + * @param ap arguments + * + * @ingroup heim_error + */ + +heim_error_code +heim_vwarnx(heim_context context, const char *fmt, va_list ap) + __attribute__ ((__format__ (__printf__, 2, 0))) +{ + return _warnerr(context, 0, 0, 1, fmt, ap); +} + +/** + * Log a warning to the log, default stderr. + * + * @param context A Kerberos 5 context. + * @param fmt message to print + * + * @ingroup heim_error + */ + +heim_error_code +heim_warnx(heim_context context, const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 2, 3))) +{ + FUNC(0, 0, 1); + return ret; +} -- cgit v1.2.3