diff options
Diffstat (limited to 'src/lib/krb5/os/expand_path.c')
-rw-r--r-- | src/lib/krb5/os/expand_path.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/src/lib/krb5/os/expand_path.c b/src/lib/krb5/os/expand_path.c new file mode 100644 index 0000000000..3e0e7f10c4 --- /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; +} |