Files
heimdal/lib/krb5/expand_path_w32.c
Asanka Herath b9dc82e09e Fix slashes while expanding path tokens
On Windows, some (external) APIs get confused if we mix both back and
forward slashes.  When expanding path tokens we use back-slashes for
token expansions because they come from the OS.  For consistency, fix
the path so that the remaining path separators are also backslashes.
2010-05-26 10:38:43 -04:00

493 lines
12 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 <shlobj.h>
#include <sddl.h>
typedef int PTYPE;
/**
* 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 int
_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_string(context, "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) {
if (context)
krb5_set_error_string(context, "strdup - Out of memory");
return ENOMEM;
}
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 int
_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 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 int
_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_string(context, "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_string(context, "Unexpected error reading token information (GLE=%d)",
GetLastError());
goto _exit;
}
if (len == 0) {
if (context)
krb5_set_error_string(context, "GetTokenInformation() returned truncated buffer");
goto _exit;
}
pOwner = malloc(len);
if (pOwner == NULL) {
if (context)
krb5_set_error_string(context, "Out of memory");
goto _exit;
}
} else {
if (context)
krb5_set_error_string(context, "GetTokenInformation() returned truncated buffer");
goto _exit;
}
if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
if (context)
krb5_set_error_string(context, "GetTokenInformation() failed. GLE=%d", GetLastError());
goto _exit;
}
if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
if (context)
krb5_set_error_string(context, "Can't convert SID to string. GLE=%d", GetLastError());
goto _exit;
}
*ret = strdup(strSid);
if (*ret == NULL && context)
krb5_set_error_string(context, "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 %{null} token
*
* The expansion of a %{null} token is always the empty string.
*/
static int
_expand_null(krb5_context context, PTYPE param, const char * postfix, char ** ret)
{
*ret = strdup("");
if (*ret == NULL) {
if (context)
krb5_set_error_string(context, "Out of memory");
return ENOMEM;
}
return 0;
}
/**
* Expand a folder identified by a CSIDL
*
* Parameters:
*
* @param[in] folder A CSIDL value identifying the folder to be
* returned.
*/
static int
_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_string(context, "Unable to determine folder path");
return 1;
}
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) {
if (context)
krb5_set_error_string(context, "Out of memory");
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 CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl
#define CSIDL(C) CSIDLP(C, NULL)
#define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f
#define SPECIAL(f) SPECIALP(f, NULL)
} tokens[] = {
/* Windows only -- */
{"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 */
/* -- end Windows only */
{"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)},
/* Expands to the "lib" directory. On
Windows this is treated the same as
the "bin" directory. */
{"BINDIR", SPECIAL(_expand_bin_dir)},
/* Expands to the "bin" directory. On
Windows this is treated the same as
the "bin" directory. */
{"LIBEXEC", SPECIAL(_expand_bin_dir)},
/* Expands to the "libexec"
directory. On Windows, this is
treated the same as the "bin"
directory. */
{"SBINDIR", SPECIAL(_expand_bin_dir)},
/* Expands to the "sbin" directory.
On Windows, this is treated the
same as the "bin" directory. */
{"TEMP", SPECIAL(_expand_temp_folder)},
/* Temporary files folder */
{"USERID", SPECIAL(_expand_userid)},
/* User ID (On Windows, this expands
to the user's string SID */
{"uid", SPECIAL(_expand_userid)},
/* Alias for USERID */
{"null", SPECIAL(_expand_null)},
/* Empty string. For compatibility. */
};
static int
_expand_token(krb5_context context, const char * token, const char * token_end,
char ** ret)
{
int i;
*ret = NULL;
if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
token_end - token <= 2) {
if (context)
krb5_set_error_string(context, "Invalid token.");
return EINVAL;
}
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_string(context, "Invalid token.");
return EINVAL;
}
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_free_path(krb5_context context,
char * path)
{
if (path == NULL)
return EINVAL;
free(path);
return 0;
}
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_expand_path_tokens(krb5_context context,
const char * path_in,
char ** ppath_out)
{
size_t len = 0;
char *tok_begin, *tok_end, *append;
const char *path_left;
*ppath_out = NULL;
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) {
if (*ppath_out)
free(*ppath_out);
*ppath_out = NULL;
if (context)
krb5_set_error_string(context, "variable missing }");
return EINVAL;
}
if (_expand_token(context, tok_begin, tok_end, &append)) {
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) {
if (*ppath_out)
free(*ppath_out);
*ppath_out = NULL;
if (context)
krb5_set_error_string(context, "malloc - out of memory");
return ENOMEM;
}
{
size_t append_len = strlen(append);
char * new_str = realloc(*ppath_out, len + append_len + 1);
if (new_str == NULL) {
free(append);
if (*ppath_out)
free(*ppath_out);
*ppath_out = NULL;
if (context)
krb5_set_error_string(context, "malloc - out of memory");
return ENOMEM;
}
*ppath_out = new_str;
memcpy(*ppath_out + len, append, append_len + 1);
len = len + append_len;
free(append);
}
}
/* Also deal with slashes */
if (*ppath_out) {
char * c;
for (c = *ppath_out; *c; c++)
if (*c == '/')
*c = '\\';
}
return 0;
}