/* -*- 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 #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 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 /* not _WIN32 */ #include 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, "%lu", (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, "%lu", (unsigned long)geteuid()) < 0) return ENOMEM; return 0; } static krb5_error_code expand_username(krb5_context context, PTYPE param, const char *postfix, char **str) { uid_t euid = geteuid(); struct passwd *pw, pwx; char pwbuf[BUFSIZ]; if (k5_getpwuid_r(euid, &pwx, pwbuf, sizeof(pwbuf), &pw) != 0) { krb5_set_error_message(context, ENOENT, _("Can't find username for uid %lu"), (unsigned long)euid); return ENOENT; } *str = strdup(pw->pw_name); if (*str == NULL) 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; PTYPE param; const char *postfix; int (*exp_func)(krb5_context, PTYPE, const char *, char **); } tokens[] = { #ifdef _WIN32 /* Roaming application data (for current user) */ {"APPDATA", CSIDL_APPDATA, NULL, expand_csidl}, /* Application data (all users) */ {"COMMON_APPDATA", CSIDL_COMMON_APPDATA, NULL, expand_csidl}, /* Local application data (for current user) */ {"LOCAL_APPDATA", CSIDL_LOCAL_APPDATA, NULL, expand_csidl}, /* Windows System folder (e.g. %WINDIR%\System32) */ {"SYSTEM", CSIDL_SYSTEM, NULL, expand_csidl}, /* Windows folder */ {"WINDOWS", CSIDL_WINDOWS, NULL, expand_csidl}, /* Per user MIT krb5 configuration file directory */ {"USERCONFIG", CSIDL_APPDATA, "\\MIT\\Kerberos5", expand_csidl}, /* Common MIT krb5 configuration file directory */ {"COMMONCONFIG", CSIDL_COMMON_APPDATA, "\\MIT\\Kerberos5", expand_csidl}, {"LIBDIR", 0, NULL, expand_bin_dir}, {"BINDIR", 0, NULL, expand_bin_dir}, {"SBINDIR", 0, NULL, expand_bin_dir}, {"euid", 0, NULL, expand_userid}, #else {"LIBDIR", 0, LIBDIR, expand_path}, {"BINDIR", 0, BINDIR, expand_path}, {"SBINDIR", 0, SBINDIR, expand_path}, {"euid", 0, NULL, expand_euid}, {"username", 0, NULL, expand_username}, #endif {"TEMP", 0, NULL, expand_temp_folder}, {"USERID", 0, NULL, expand_userid}, {"uid", 0, NULL, expand_userid}, {"null", 0, NULL, 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; }