krb5 commit: Add internal path expansion functions

Greg Hudson ghudson at MIT.EDU
Tue Jul 24 16:36:28 EDT 2012


https://github.com/krb5/krb5/commit/7d07dc63a22bfdebc24f0368f969acc4b76d372c
commit 7d07dc63a22bfdebc24f0368f969acc4b76d372c
Author: Greg Hudson <ghudson at mit.edu>
Date:   Tue Jul 24 16:26:27 2012 -0400

    Add internal path expansion functions
    
    Add an adapted version of Heimdal's expand_path.c, defining
    k5_expand_path_tokens() and k5_expand_path_tokens_extra().  These
    functions allow template paths like %{TEMP}/krb5cc_%{uid} to be
    resolved.  Also add a test program to exercise the path expansion
    code.

 src/lib/krb5/libkrb5.exports    |    2 +
 src/lib/krb5/os/Makefile.in     |   23 ++-
 src/lib/krb5/os/expand_path.c   |  534 +++++++++++++++++++++++++++++++++++++++
 src/lib/krb5/os/os-proto.h      |    6 +
 src/lib/krb5/os/t_expand_path.c |   16 ++
 5 files changed, 577 insertions(+), 4 deletions(-)

diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index e5acff2..28049e7 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -101,6 +101,8 @@ k5_ccselect_free_context
 k5_copy_etypes
 k5_count_etypes
 k5_etypes_contains
+k5_expand_path_tokens
+k5_expand_path_tokens_extra
 k5_free_serverlist
 k5_kt_get_principal
 k5_locate_kdc
diff --git a/src/lib/krb5/os/Makefile.in b/src/lib/krb5/os/Makefile.in
index 7d07480..2bbfb3f 100644
--- a/src/lib/krb5/os/Makefile.in
+++ b/src/lib/krb5/os/Makefile.in
@@ -4,7 +4,8 @@ KRB5_RUN_ENV = @KRB5_RUN_ENV@
 PROG_LIBPATH=-L$(TOPLIBD)
 PROG_RPATH=$(KRB5_LIBDIR)
 DEFS=
-DEFINES=-DLIBDIR=\"$(KRB5_LIBDIR)\"
+DEFINES=-DLIBDIR=\"$(KRB5_LIBDIR)\" -DBINDIR=\"$(CLIENT_BINDIR)\" \
+	-DSBINDIR=\"$(ADMIN_BINDIR)\"
 LOCALINCLUDES=-I$(top_srcdir)/util/profile
 
 ##DOS##BUILDTOP = ..\..\..
@@ -21,6 +22,7 @@ STLIBOBJS= \
 	cm.o		\
 	dnsglue.o	\
 	dnssrv.o	\
+	expand_path.o	\
 	free_krbhs.o	\
 	full_ipadr.o	\
 	get_krbhst.o	\
@@ -65,6 +67,7 @@ OBJS= \
 	$(OUTPRE)cm.$(OBJEXT)		\
 	$(OUTPRE)dnsglue.$(OBJEXT)	\
 	$(OUTPRE)dnssrv.$(OBJEXT)	\
+	$(OUTPRE)expand_path.$(OBJEXT)	\
 	$(OUTPRE)free_krbhs.$(OBJEXT)	\
 	$(OUTPRE)full_ipadr.$(OBJEXT)	\
 	$(OUTPRE)get_krbhst.$(OBJEXT)	\
@@ -109,6 +112,7 @@ SRCS= \
 	$(srcdir)/cm.c		\
 	$(srcdir)/dnsglue.c	\
 	$(srcdir)/dnssrv.c	\
+	$(srcdir)/expand_path.c	\
 	$(srcdir)/free_krbhs.c	\
 	$(srcdir)/full_ipadr.c	\
 	$(srcdir)/get_krbhst.c	\
@@ -144,7 +148,7 @@ SRCS= \
 	$(srcdir)/write_msg.c
 
 EXTRADEPSRCS = \
-	t_an_to_ln.c t_gifconf.c t_locate_kdc.c \
+	t_an_to_ln.c t_expand_path.c t_gifconf.c t_locate_kdc.c \
 	t_std_conf.c
 
 ##DOS##LIBOBJS = $(OBJS)
@@ -155,7 +159,7 @@ clean-unix:: clean-libobjs
 shared:
 	mkdir shared
 
-TEST_PROGS= t_std_conf t_an_to_ln t_kuserok t_locate_kdc t_trace
+TEST_PROGS= t_std_conf t_an_to_ln t_kuserok t_locate_kdc t_trace t_expand_path
 
 T_STD_CONF_OBJS= t_std_conf.o 
 
@@ -188,6 +192,9 @@ $(OUTPRE)t_locate_kdc.exe: $(OUTPRE)t_locate_kdc.obj \
 t_trace: $(T_TRACE_OBJS) $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o t_trace $(T_TRACE_OBJS) $(KRB5_BASE_LIBS)
 
+t_expand_path: t_expand_path.o $(KRB5_BASE_DEPLIBS)
+	$(CC_LINK) -o $@ t_expand_path.o $(KRB5_BASE_LIBS)
+
 LCLINT=lclint
 LCLINTOPTS= -warnposix \
 	-usedef +charintliteral +ignoresigns -predboolint +boolint \
@@ -198,7 +205,7 @@ lclint-localaddr: localaddr.c
 		-DTEST $(srcdir)/localaddr.c
 
 check-unix:: check-unix-stdconf check-unix-locate check-unix-antoln \
-	check-unix-trace t_kuserok
+	check-unix-trace check-unix-expand t_kuserok
 
 check-unix-stdconf:: t_std_conf
 	KRB5_CONFIG=$(srcdir)/td_krb5.conf ; export KRB5_CONFIG ;\
@@ -267,6 +274,14 @@ check-unix-trace:: t_trace
 	sed -e 's/^[^:]*: //' t_trace.out | cmp - $(srcdir)/t_trace.ref
 	rm -f t_trace.out
 
+check-unix-expand:: t_expand_path
+	$(KRB5_RUN_ENV) $(VALGRIND) ./t_expand_path '%{null}' ''
+	$(KRB5_RUN_ENV) $(VALGRIND) ./t_expand_path ' %{BINDIR}%{LIBDIR} ' \
+		' $(CLIENT_BINDIR)$(KRB5_LIBDIR) '
+	$(KRB5_RUN_ENV) $(VALGRIND) ./t_expand_path \
+		'the %{animal}%{s} on the %{place}%{s}' \
+		'the frogs on the pads'
+
 clean:: 
 	$(RM) $(TEST_PROGS) test.out t_std_conf.o t_an_to_ln.o t_locate_kdc.o
 	$(RM) t_kuserok.o
diff --git a/src/lib/krb5/os/expand_path.c b/src/lib/krb5/os/expand_path.c
new file mode 100644
index 0000000..3e0e7f1
--- /dev/null
+++ b/src/lib/krb5/os/expand_path.c
@@ -0,0 +1,534 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/os/expand_path.c - Parameterized path expansion facility */
+/*
+ * Copyright (c) 2009, 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 "k5-int.h"
+#include "os-proto.h"
+
+typedef int PTYPE;
+
+#ifdef _WIN32
+#include <shlobj.h>
+#include <sddl.h>
+
+/*
+ * 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 krb5_error_code
+expand_temp_folder(krb5_context context, PTYPE param, const char *postfix,
+                   char **ret)
+{
+    TCHAR tpath[MAX_PATH];
+    size_t len;
+
+    if (!GetTempPath(sizeof(tpath) / sizeof(tpath[0]), tpath)) {
+        krb5_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 ENOMEM;
+
+    return 0;
+}
+
+/*
+ * 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
+ * krb5.dll is located.
+ */
+static krb5_error_code
+expand_bin_dir(krb5_context context, PTYPE param, const char *postfix,
+               char **ret)
+{
+    TCHAR path[MAX_PATH];
+    TCHAR *lastSlash;
+    DWORD nc;
+
+    nc = GetModuleFileName(get_lib_instance(), 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 ENOMEM;
+
+    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 krb5_error_code
+expand_userid(krb5_context context, PTYPE param, const char *postfix,
+              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) {
+            krb5_set_error_message(context, rv,
+                                   "Can't open thread token (GLE=%d)", le);
+            goto cleanup;
+        }
+    }
+
+    if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) {
+        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+            krb5_set_error_message(context, rv,
+                                   "Unexpected error reading token "
+                                   "information (GLE=%d)", GetLastError());
+            goto cleanup;
+        }
+
+        if (len == 0) {
+            krb5_set_error_message(context, rv, "GetTokenInformation() "
+                                   "returned truncated buffer");
+            goto cleanup;
+        }
+
+        pOwner = malloc(len);
+        if (pOwner == NULL) {
+            rv = ENOMEM;
+            goto cleanup;
+        }
+    } else {
+        krb5_set_error_message(context, rv, "GetTokenInformation() returned "
+                               "truncated buffer");
+        goto cleanup;
+    }
+
+    if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
+        krb5_set_error_message(context, rv, "GetTokenInformation() failed. "
+                               "GLE=%d", GetLastError());
+        goto cleanup;
+    }
+
+    if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
+        krb5_set_error_message(context, rv, "Can't convert SID to string. "
+                               "GLE=%d", GetLastError());
+        goto cleanup;
+    }
+
+    *ret = strdup(strSid);
+    if (*ret == NULL) {
+        rv = ENOMEM;
+        goto cleanup;
+    }
+
+    rv = 0;
+
+cleanup:
+    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 krb5_error_code
+expand_csidl(krb5_context context, PTYPE folder, const char *postfix,
+             char **ret)
+{
+    TCHAR path[MAX_PATH];
+    size_t len;
+
+    if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT,
+                        path) != S_OK) {
+        krb5_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 ENOMEM;
+
+    *ret = strdup(path);
+    if (*ret == NULL)
+        return ENOMEM;
+    return 0;
+}
+
+#else
+
+static krb5_error_code
+expand_path(krb5_context context, PTYPE param, const char *postfix, char **ret)
+{
+    *ret = strdup(postfix);
+    if (*ret == NULL)
+        return ENOMEM;
+    return 0;
+}
+
+static krb5_error_code
+expand_temp_folder(krb5_context context, PTYPE param, const char *postfix,
+                   char **ret)
+{
+    const char *p = NULL;
+
+    if (context == NULL || !context->profile_secure)
+        p = getenv("TMPDIR");
+    *ret = strdup((p != NULL) ? p : "/tmp");
+    if (*ret == NULL)
+        return ENOMEM;
+    return 0;
+}
+
+static krb5_error_code
+expand_userid(krb5_context context, PTYPE param, const char *postfix,
+              char **str)
+{
+    if (asprintf(str, "%ld", (unsigned long)getuid()) < 0)
+        return ENOMEM;
+    return 0;
+}
+
+static krb5_error_code
+expand_euid(krb5_context context, PTYPE param, const char *postfix, char **str)
+{
+    if (asprintf(str, "%ld", (unsigned long)geteuid()) < 0)
+        return ENOMEM;
+    return 0;
+}
+
+#endif /* not _WIN32 */
+
+/*
+ * Expand an extra token
+ */
+static krb5_error_code
+expand_extra_token(krb5_context context, const char *value, char **ret)
+{
+    *ret = strdup(value);
+    if (*ret == NULL)
+        return ENOMEM;
+    return 0;
+}
+
+/*
+ * Expand a %{null} token
+ *
+ * The expansion of a %{null} token is always the empty string.
+ */
+static krb5_error_code
+expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret)
+{
+    *ret = strdup("");
+    if (*ret == NULL)
+        return ENOMEM;
+    return 0;
+}
+
+
+static const struct token {
+    const char *tok;
+    int ftype;
+#define FTYPE_CSIDL 0
+#define FTYPE_SPECIAL 1
+
+    PTYPE param;
+    const char *postfix;
+
+    int (*exp_func)(krb5_context, PTYPE, 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)
+
+    /* Roaming application data (for current user) */
+    {"APPDATA", CSIDL(CSIDL_APPDATA)},
+    /* Application data (all users) */
+    {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)},
+    /* Local application data (for current user) */
+    {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)},
+    /* Windows System folder (e.g. %WINDIR%\System32) */
+    {"SYSTEM", CSIDL(CSIDL_SYSTEM)},
+    /* Windows folder */
+    {"WINDOWS", CSIDL(CSIDL_WINDOWS)},
+    /* Per user MIT krb5 configuration file directory */
+    {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\MIT\\Kerberos5")},
+    /* Common MIT krb5 configuration file directory */
+    {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\MIT\\Kerberos5")},
+    {"LIBDIR", SPECIAL(expand_bin_dir)},
+    {"BINDIR", SPECIAL(expand_bin_dir)},
+    {"SBINDIR", SPECIAL(expand_bin_dir)},
+    {"euid", SPECIAL(expand_userid)},
+#else
+    {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, expand_path},
+    {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, expand_path},
+    {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, expand_path},
+    {"euid", SPECIAL(expand_euid)},
+#endif
+    {"TEMP", SPECIAL(expand_temp_folder)},
+    {"USERID", SPECIAL(expand_userid)},
+    {"uid", SPECIAL(expand_userid)},
+    {"null", SPECIAL(expand_null)}
+};
+
+static krb5_error_code
+expand_token(krb5_context context, const char *token, const char *token_end,
+             char **extra_tokens, char **ret)
+{
+    size_t i;
+    char **p;
+
+    *ret = NULL;
+
+    if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
+        token_end - token <= 2) {
+        krb5_set_error_message(context, EINVAL, _("Invalid token"));
+        return EINVAL;
+    }
+
+    for (p = extra_tokens; p != NULL && *p != NULL; p += 2) {
+        if (strncmp(token + 2, *p, (token_end - token) - 2) == 0)
+            return expand_extra_token(context, p[1], ret);
+    }
+
+    for (i = 0; i < sizeof(tokens) / sizeof(tokens[0]); i++) {
+        if (!strncmp(token + 2, tokens[i].tok, (token_end - token) - 2)) {
+            return tokens[i].exp_func(context, tokens[i].param,
+                                      tokens[i].postfix, ret);
+        }
+    }
+
+    krb5_set_error_message(context, EINVAL, _("Invalid token"));
+    return EINVAL;
+}
+
+/*
+ * Expand tokens in path_in to produce *path_out.  The caller should free
+ * *path_out with free().
+ */
+krb5_error_code
+k5_expand_path_tokens(krb5_context context, const char *path_in,
+                      char **path_out)
+{
+    return k5_expand_path_tokens_extra(context, path_in, path_out, NULL);
+}
+
+static void
+free_extra_tokens(char **extra_tokens)
+{
+    char **p;
+
+    for (p = extra_tokens; p != NULL && *p != NULL; p++)
+        free(*p);
+    free(extra_tokens);
+}
+
+/*
+ * Expand tokens in path_in to produce *path_out.  Arguments after path_out are
+ * pairs of extra token names and replacement values, terminated by a NULL.
+ * The caller should free *path_out with free().
+ */
+krb5_error_code
+k5_expand_path_tokens_extra(krb5_context context, const char *path_in,
+                            char **path_out, ...)
+{
+    krb5_error_code ret;
+    struct k5buf buf;
+    char *tok_begin, *tok_end, *tok_val, *path, **extra_tokens = NULL;
+    const char *path_left;
+    size_t nargs = 0, i;
+    va_list ap;
+
+    *path_out = NULL;
+
+    krb5int_buf_init_dynamic(&buf);
+
+    /* Count extra tokens. */
+    va_start(ap, path_out);
+    while (va_arg(ap, const char *) != NULL)
+        nargs++;
+    va_end(ap);
+    if (nargs % 2 != 0)
+        return EINVAL;
+
+    /* Get extra tokens. */
+    if (nargs > 0) {
+        extra_tokens = k5alloc((nargs + 1) * sizeof(char *), &ret);
+        if (extra_tokens == NULL)
+            goto cleanup;
+        va_start(ap, path_out);
+        for (i = 0; i < nargs; i++) {
+            extra_tokens[i] = strdup(va_arg(ap, const char *));
+            if (extra_tokens[i] == NULL) {
+                ret = ENOMEM;
+                goto cleanup;
+            }
+        }
+        va_end(ap);
+    }
+
+    path_left = path_in;
+    while (TRUE) {
+        /* Find the next token in path_left and add the literal text up to it.
+         * If there are no more tokens, we can finish up. */
+        tok_begin = strstr(path_left, "%{");
+        if (tok_begin == NULL) {
+            krb5int_buf_add(&buf, path_left);
+            break;
+        }
+        krb5int_buf_add_len(&buf, path_left, tok_begin - path_left);
+
+        /* Find the end of this token. */
+        tok_end = strchr(tok_begin, '}');
+        if (tok_end == NULL) {
+            ret = EINVAL;
+            krb5_set_error_message(context, ret, _("variable missing }"));
+            goto cleanup;
+        }
+
+        /* Expand this token and add its value. */
+        ret = expand_token(context, tok_begin, tok_end, extra_tokens,
+                           &tok_val);
+        if (ret)
+            goto cleanup;
+        krb5int_buf_add(&buf, tok_val);
+        free(tok_val);
+        path_left = tok_end + 1;
+    }
+
+    path = krb5int_buf_data(&buf);
+    if (path == NULL) {
+        ret = ENOMEM;
+        goto cleanup;
+    }
+#ifdef _WIN32
+    /* Also deal with slashes. */
+    {
+        char *p;
+        for (p = path; *p != '\0'; p++) {
+            if (*p == '/')
+                *p = '\\';
+        }
+    }
+#endif
+    *path_out = path;
+
+cleanup:
+    if (*path_out == NULL)
+        krb5int_free_buf(&buf);
+    free_extra_tokens(extra_tokens);
+    return 0;
+}
diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h
index 665187c..ec97545 100644
--- a/src/lib/krb5/os/os-proto.h
+++ b/src/lib/krb5/os/os-proto.h
@@ -107,6 +107,12 @@ int krb5int_net_writev (krb5_context, int, sg_buf *, int);
 
 int k5_getcurtime(struct timeval *tvp);
 
+krb5_error_code k5_expand_path_tokens(krb5_context context,
+                                      const char *path_in, char **path_out);
+krb5_error_code k5_expand_path_tokens_extra(krb5_context context,
+                                            const char *path_in,
+                                            char **path_out, ...);
+
 #include "k5-thread.h"
 extern k5_mutex_t krb5int_us_time_mutex;
 
diff --git a/src/lib/krb5/os/t_expand_path.c b/src/lib/krb5/os/t_expand_path.c
new file mode 100644
index 0000000..b318ff9
--- /dev/null
+++ b/src/lib/krb5/os/t_expand_path.c
@@ -0,0 +1,16 @@
+#include "k5-int.h"
+#include "os-proto.h"
+
+int
+main(int argc, char **argv)
+{
+    char *path;
+
+    if (k5_expand_path_tokens_extra(NULL, argv[1], &path, "animal", "frog",
+				    "place", "pad", "s", "s", NULL) != 0)
+	return 2;
+    if (strcmp(path, argv[2]) != 0)
+	return 1;
+    free(path);
+    return 0;
+}


More information about the cvs-krb5 mailing list