
Commit ad7e54d698
introduced the use
of _krb5_expand_path_tokens() to expand tokens (and on Windows convert
path delimiters) within credential cache names. This is safe to do
for the path based credential cache types FILE, DIR and SCC but on
Windows is unsafe for the non-path types.
For example on Windows, the API credential cache names are often based
on the principal name and the principal name is parsed from the ccname.
This practice was introduced with the version v2 ccapi when there was
no method of enumerating the caches from the krb5 library.
This change adds a "filepath" boolean parameter to _krb5_expand_path_tokens()
which is set to TRUE (non-zero) when the input is a file path and FALSE
(zero) when the input is not a file path. _krb5_expand_path_tokens() will
only perform directory separator normalization on Windows when the
"filepath" parameter is TRUE.
This change is not the preferred solution because it requires that the
library be aware of all credential cache types that use path based
residuals. The preferred solution would require that the credential cache
implementation indicate whether or not it uses a path based residual.
This change has been implemented using a prefix test and not a change to
struct krb5_cc_ops because existing ccache plugins will not know how to
advertise their use of path based residuals and that path expansion is
safe.
Change-Id: I8135991e8ce69fc5273d381ea9c2078bc2bcd19a
604 lines
15 KiB
C
604 lines
15 KiB
C
|
|
/***********************************************************************
|
|
* 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 "krb5_locl.h"
|
|
|
|
#include <stdarg.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)) {
|
|
if (context)
|
|
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 krb5_enomem(context);
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern HINSTANCE _krb5_hInstance;
|
|
|
|
/*
|
|
* 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(_krb5_hInstance, 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 krb5_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 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) {
|
|
if (context)
|
|
krb5_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) {
|
|
if (context)
|
|
krb5_set_error_message(context, rv,
|
|
"Unexpected error reading token information (GLE=%d)",
|
|
GetLastError());
|
|
goto _exit;
|
|
}
|
|
|
|
if (len == 0) {
|
|
if (context)
|
|
krb5_set_error_message(context, rv,
|
|
"GetTokenInformation() returned truncated buffer");
|
|
goto _exit;
|
|
}
|
|
|
|
pOwner = malloc(len);
|
|
if (pOwner == NULL) {
|
|
if (context)
|
|
krb5_set_error_message(context, rv, "Out of memory");
|
|
goto _exit;
|
|
}
|
|
} else {
|
|
if (context)
|
|
krb5_set_error_message(context, rv, "GetTokenInformation() returned truncated buffer");
|
|
goto _exit;
|
|
}
|
|
|
|
if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
|
|
if (context)
|
|
krb5_set_error_message(context, rv, "GetTokenInformation() failed. GLE=%d", GetLastError());
|
|
goto _exit;
|
|
}
|
|
|
|
if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
|
|
if (context)
|
|
krb5_set_error_message(context, rv, "Can't convert SID to string. GLE=%d", GetLastError());
|
|
goto _exit;
|
|
}
|
|
|
|
*ret = strdup(strSid);
|
|
if (*ret == NULL && context)
|
|
krb5_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 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) {
|
|
if (context)
|
|
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 krb5_enomem(context);
|
|
|
|
*ret = strdup(path);
|
|
if (*ret == NULL)
|
|
return krb5_enomem(context);
|
|
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 krb5_enomem(context);
|
|
return 0;
|
|
}
|
|
|
|
static krb5_error_code
|
|
_expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
|
|
{
|
|
const char *p = NULL;
|
|
|
|
if (!issuid())
|
|
p = getenv("TEMP");
|
|
|
|
if (p)
|
|
*ret = strdup(p);
|
|
else
|
|
*ret = strdup("/tmp");
|
|
if (*ret == NULL)
|
|
return krb5_enomem(context);
|
|
return 0;
|
|
}
|
|
|
|
static krb5_error_code
|
|
_expand_userid(krb5_context context, PTYPE param, const char *postfix, char **str)
|
|
{
|
|
int ret = asprintf(str, "%ld", (unsigned long)getuid());
|
|
if (ret < 0 || *str == NULL)
|
|
return krb5_enomem(context);
|
|
return 0;
|
|
}
|
|
|
|
|
|
#endif /* _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 krb5_enomem(context);
|
|
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 krb5_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)(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)
|
|
|
|
{"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
|
|
{"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},
|
|
#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) {
|
|
if (context)
|
|
krb5_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 (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);
|
|
}
|
|
|
|
if (context)
|
|
krb5_set_error_message(context, EINVAL, "Invalid token.");
|
|
return EINVAL;
|
|
}
|
|
|
|
/**
|
|
* Internal function to expand tokens in paths.
|
|
*
|
|
* Inputs:
|
|
*
|
|
* @context A krb5_context
|
|
* @path_in The path to expand tokens from
|
|
*
|
|
* Outputs:
|
|
*
|
|
* @ppath_out Path with expanded tokens (caller must free() this)
|
|
*/
|
|
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
|
|
_krb5_expand_path_tokens(krb5_context context,
|
|
const char *path_in,
|
|
int filepath,
|
|
char **ppath_out)
|
|
{
|
|
return _krb5_expand_path_tokensv(context, path_in, filepath, ppath_out, NULL);
|
|
}
|
|
|
|
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 krb5_context
|
|
* @path_in The path to expand tokens from
|
|
* @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.
|
|
*
|
|
* Outputs:
|
|
*
|
|
* @ppath_out Path with expanded tokens (caller must free() this)
|
|
*/
|
|
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
|
|
_krb5_expand_path_tokensv(krb5_context context,
|
|
const char *path_in,
|
|
int filepath,
|
|
char **ppath_out, ...)
|
|
{
|
|
char *tok_begin, *tok_end, *append;
|
|
char **extra_tokens = NULL;
|
|
const char *path_left;
|
|
const char *s;
|
|
size_t nargs = 0;
|
|
size_t len = 0;
|
|
va_list ap;
|
|
|
|
if (path_in == NULL || *path_in == '\0') {
|
|
*ppath_out = strdup("");
|
|
return 0;
|
|
}
|
|
|
|
*ppath_out = NULL;
|
|
|
|
va_start(ap, ppath_out);
|
|
while ((s = va_arg(ap, const char *))) {
|
|
nargs++;
|
|
s = va_arg(ap, const char *);
|
|
}
|
|
va_end(ap);
|
|
nargs *= 2;
|
|
|
|
/* Get extra tokens */
|
|
if (nargs) {
|
|
size_t i;
|
|
|
|
extra_tokens = calloc(nargs + 1, sizeof (*extra_tokens));
|
|
if (extra_tokens == NULL)
|
|
return krb5_enomem(context);
|
|
va_start(ap, ppath_out);
|
|
for (i = 0; i < nargs; i++) {
|
|
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 krb5_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 krb5_enomem(context);
|
|
}
|
|
}
|
|
va_end(ap);
|
|
}
|
|
|
|
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;
|
|
if (context)
|
|
krb5_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 krb5_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 krb5_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;
|
|
}
|