Fix slashes while expanding path tokens
[heimdal.git] / lib / krb5 / expand_path_w32.c
blob0c4287c32e8d1986ece223372ab6d62ed231cab6
1 /***********************************************************************
2 * Copyright (c) 2009, Secure Endpoints Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 **********************************************************************/
32 #include "krb5_locl.h"
33 #include <shlobj.h>
34 #include <sddl.h>
36 typedef int PTYPE;
38 /**
39 * Expand a %{TEMP} token
41 * The %{TEMP} token expands to the temporary path for the current
42 * user as returned by GetTempPath().
44 * @note: Since the GetTempPath() function relies on the TMP or TEMP
45 * environment variables, this function will failover to the system
46 * temporary directory until the user profile is loaded. In addition,
47 * the returned path may or may not exist.
49 static int
50 _expand_temp_folder(krb5_context context, PTYPE param, const char * postfix, char ** ret)
52 TCHAR tpath[MAX_PATH];
53 size_t len;
55 if (!GetTempPath(sizeof(tpath)/sizeof(tpath[0]), tpath)) {
56 if (context)
57 krb5_set_error_string(context, "Failed to get temporary path (GLE=%d)",
58 GetLastError());
59 return EINVAL;
62 len = strlen(tpath);
64 if (len > 0 && tpath[len - 1] == '\\')
65 tpath[len - 1] = '\0';
67 *ret = strdup(tpath);
69 if (*ret == NULL) {
70 if (context)
71 krb5_set_error_string(context, "strdup - Out of memory");
72 return ENOMEM;
75 return 0;
78 extern HINSTANCE _krb5_hInstance;
80 /**
81 * Expand a %{BINDIR} token
83 * This is also used to expand a few other tokens on Windows, since
84 * most of the executable binaries end up in the same directory. The
85 * "bin" directory is considered to be the directory in which the
86 * krb5.dll is located.
88 static int
89 _expand_bin_dir(krb5_context context, PTYPE param, const char * postfix, char ** ret)
91 TCHAR path[MAX_PATH];
92 TCHAR *lastSlash;
93 DWORD nc;
95 nc = GetModuleFileName(_krb5_hInstance, path, sizeof(path)/sizeof(path[0]));
96 if (nc == 0 ||
97 nc == sizeof(path)/sizeof(path[0])) {
98 return EINVAL;
101 lastSlash = strrchr(path, '\\');
102 if (lastSlash != NULL) {
103 TCHAR *fslash = strrchr(lastSlash, '/');
105 if (fslash != NULL)
106 lastSlash = fslash;
108 *lastSlash = '\0';
111 if (postfix) {
112 if (strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0]))
113 return EINVAL;
116 *ret = strdup(path);
117 if (*ret == NULL)
118 return ENOMEM;
120 return 0;
124 * Expand a %{USERID} token
126 * The %{USERID} token expands to the string representation of the
127 * user's SID. The user account that will be used is the account
128 * corresponding to the current thread's security token. This means
129 * that:
131 * - If the current thread token has the anonymous impersonation
132 * level, the call will fail.
134 * - If the current thread is impersonating a token at
135 * SecurityIdentification level the call will fail.
138 static int
139 _expand_userid(krb5_context context, PTYPE param, const char * postfix, char ** ret)
141 int rv = EINVAL;
142 HANDLE hThread = NULL;
143 HANDLE hToken = NULL;
144 PTOKEN_OWNER pOwner = NULL;
145 DWORD len = 0;
146 LPTSTR strSid = NULL;
148 hThread = GetCurrentThread();
150 if (!OpenThreadToken(hThread, TOKEN_QUERY,
151 FALSE, /* Open the thread token as the
152 current thread user. */
153 &hToken)) {
155 DWORD le = GetLastError();
157 if (le == ERROR_NO_TOKEN) {
158 HANDLE hProcess = GetCurrentProcess();
160 le = 0;
161 if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
162 le = GetLastError();
165 if (le != 0) {
166 if (context)
167 krb5_set_error_string(context, "Can't open thread token (GLE=%d)", le);
168 goto _exit;
172 if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) {
173 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
174 if (context)
175 krb5_set_error_string(context, "Unexpected error reading token information (GLE=%d)",
176 GetLastError());
177 goto _exit;
180 if (len == 0) {
181 if (context)
182 krb5_set_error_string(context, "GetTokenInformation() returned truncated buffer");
183 goto _exit;
186 pOwner = malloc(len);
187 if (pOwner == NULL) {
188 if (context)
189 krb5_set_error_string(context, "Out of memory");
190 goto _exit;
192 } else {
193 if (context)
194 krb5_set_error_string(context, "GetTokenInformation() returned truncated buffer");
195 goto _exit;
198 if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
199 if (context)
200 krb5_set_error_string(context, "GetTokenInformation() failed. GLE=%d", GetLastError());
201 goto _exit;
204 if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
205 if (context)
206 krb5_set_error_string(context, "Can't convert SID to string. GLE=%d", GetLastError());
207 goto _exit;
210 *ret = strdup(strSid);
211 if (*ret == NULL && context)
212 krb5_set_error_string(context, "Out of memory");
214 rv = 0;
216 _exit:
217 if (hToken != NULL)
218 CloseHandle(hToken);
220 if (pOwner != NULL)
221 free (pOwner);
223 if (strSid != NULL)
224 LocalFree(strSid);
226 return rv;
230 * Expand a %{null} token
232 * The expansion of a %{null} token is always the empty string.
234 static int
235 _expand_null(krb5_context context, PTYPE param, const char * postfix, char ** ret)
237 *ret = strdup("");
238 if (*ret == NULL) {
239 if (context)
240 krb5_set_error_string(context, "Out of memory");
241 return ENOMEM;
243 return 0;
248 * Expand a folder identified by a CSIDL
250 * Parameters:
252 * @param[in] folder A CSIDL value identifying the folder to be
253 * returned.
255 static int
256 _expand_csidl(krb5_context context, PTYPE folder, const char * postfix, char ** ret)
258 TCHAR path[MAX_PATH];
259 size_t len;
261 if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path) != S_OK) {
262 if (context)
263 krb5_set_error_string(context, "Unable to determine folder path");
264 return 1;
267 len = strlen(path);
269 if (len > 0 && path[len - 1] == '\\')
270 path[len - 1] = '\0';
272 if (postfix &&
273 strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0])) {
274 return ENOMEM;
277 *ret = strdup(path);
278 if (*ret == NULL) {
279 if (context)
280 krb5_set_error_string(context, "Out of memory");
281 return ENOMEM;
283 return 0;
286 static const struct token {
287 const char * tok;
288 int ftype;
289 #define FTYPE_CSIDL 0
290 #define FTYPE_SPECIAL 1
292 PTYPE param;
293 const char * postfix;
295 int (*exp_func)(krb5_context, PTYPE, const char *, char **);
297 #define CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl
298 #define CSIDL(C) CSIDLP(C, NULL)
300 #define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f
301 #define SPECIAL(f) SPECIALP(f, NULL)
303 } tokens[] = {
304 /* Windows only -- */
305 {"APPDATA", CSIDL(CSIDL_APPDATA)},
306 /* Roaming application data (for current user) */
308 {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)},
309 /* Application data (all users) */
311 {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)},
312 /* Local application data (for current user) */
314 {"SYSTEM", CSIDL(CSIDL_SYSTEM)},
315 /* Windows System folder (e.g. %WINDIR%\System32) */
317 {"WINDOWS", CSIDL(CSIDL_WINDOWS)},
318 /* Windows folder */
319 /* -- end Windows only */
321 {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)},
322 /* Per user Heimdal configuration file path */
324 {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)},
325 /* Common Heimdal configuration file path */
327 {"LIBDIR", SPECIAL(_expand_bin_dir)},
328 /* Expands to the "lib" directory. On
329 Windows this is treated the same as
330 the "bin" directory. */
332 {"BINDIR", SPECIAL(_expand_bin_dir)},
333 /* Expands to the "bin" directory. On
334 Windows this is treated the same as
335 the "bin" directory. */
337 {"LIBEXEC", SPECIAL(_expand_bin_dir)},
338 /* Expands to the "libexec"
339 directory. On Windows, this is
340 treated the same as the "bin"
341 directory. */
343 {"SBINDIR", SPECIAL(_expand_bin_dir)},
344 /* Expands to the "sbin" directory.
345 On Windows, this is treated the
346 same as the "bin" directory. */
348 {"TEMP", SPECIAL(_expand_temp_folder)},
349 /* Temporary files folder */
351 {"USERID", SPECIAL(_expand_userid)},
352 /* User ID (On Windows, this expands
353 to the user's string SID */
355 {"uid", SPECIAL(_expand_userid)},
356 /* Alias for USERID */
358 {"null", SPECIAL(_expand_null)},
359 /* Empty string. For compatibility. */
363 static int
364 _expand_token(krb5_context context, const char * token, const char * token_end,
365 char ** ret)
367 int i;
369 *ret = NULL;
371 if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
372 token_end - token <= 2) {
373 if (context)
374 krb5_set_error_string(context, "Invalid token.");
375 return EINVAL;
378 for (i=0; i < sizeof(tokens)/sizeof(tokens[0]); i++) {
379 if (!strncmp(token+2, tokens[i].tok, (token_end - token) - 2))
380 return tokens[i].exp_func(context, tokens[i].param, tokens[i].postfix, ret);
383 if (context)
384 krb5_set_error_string(context, "Invalid token.");
385 return EINVAL;
388 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
389 _krb5_free_path(krb5_context context,
390 char * path)
392 if (path == NULL)
393 return EINVAL;
395 free(path);
396 return 0;
399 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
400 _krb5_expand_path_tokens(krb5_context context,
401 const char * path_in,
402 char ** ppath_out)
404 size_t len = 0;
405 char *tok_begin, *tok_end, *append;
406 const char *path_left;
408 *ppath_out = NULL;
410 for (path_left = path_in; path_left && *path_left; ) {
412 tok_begin = strstr(path_left, "%{");
414 if (tok_begin && tok_begin != path_left) {
416 append = malloc((tok_begin - path_left) + 1);
417 if (append) {
418 memcpy(append, path_left, tok_begin - path_left);
419 append[tok_begin - path_left] = '\0';
421 path_left = tok_begin;
423 } else if (tok_begin) {
425 tok_end = strchr(tok_begin, '}');
426 if (tok_end == NULL) {
427 if (*ppath_out)
428 free(*ppath_out);
429 *ppath_out = NULL;
430 if (context)
431 krb5_set_error_string(context, "variable missing }");
432 return EINVAL;
435 if (_expand_token(context, tok_begin, tok_end, &append)) {
436 if (*ppath_out)
437 free(*ppath_out);
438 *ppath_out = NULL;
439 return EINVAL;
442 path_left = tok_end + 1;
443 } else {
445 append = strdup(path_left);
446 path_left = NULL;
450 if (append == NULL) {
452 if (*ppath_out)
453 free(*ppath_out);
454 *ppath_out = NULL;
455 if (context)
456 krb5_set_error_string(context, "malloc - out of memory");
457 return ENOMEM;
462 size_t append_len = strlen(append);
463 char * new_str = realloc(*ppath_out, len + append_len + 1);
465 if (new_str == NULL) {
466 free(append);
467 if (*ppath_out)
468 free(*ppath_out);
469 *ppath_out = NULL;
470 if (context)
471 krb5_set_error_string(context, "malloc - out of memory");
472 return ENOMEM;
475 *ppath_out = new_str;
476 memcpy(*ppath_out + len, append, append_len + 1);
477 len = len + append_len;
478 free(append);
482 /* Also deal with slashes */
483 if (*ppath_out) {
484 char * c;
485 for (c = *ppath_out; *c; c++)
486 if (*c == '/')
487 *c = '\\';
490 return 0;