resurrect password change support again
[heimdal.git] / lib / krb5 / expand_path.c
blob2111fea535f9699f0a94dfe15372f0c2d5209e09
2 /***********************************************************************
3 * Copyright (c) 2009, Secure Endpoints Inc.
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 **********************************************************************/
33 #include "krb5_locl.h"
35 #include <stdarg.h>
37 typedef int PTYPE;
39 #ifdef _WIN32
40 #include <shlobj.h>
41 #include <sddl.h>
44 * Expand a %{TEMP} token
46 * The %{TEMP} token expands to the temporary path for the current
47 * user as returned by GetTempPath().
49 * @note: Since the GetTempPath() function relies on the TMP or TEMP
50 * environment variables, this function will failover to the system
51 * temporary directory until the user profile is loaded. In addition,
52 * the returned path may or may not exist.
54 static krb5_error_code
55 _expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
57 TCHAR tpath[MAX_PATH];
58 size_t len;
60 if (!GetTempPath(sizeof(tpath)/sizeof(tpath[0]), tpath)) {
61 if (context)
62 krb5_set_error_message(context, EINVAL,
63 "Failed to get temporary path (GLE=%d)",
64 GetLastError());
65 return EINVAL;
68 len = strlen(tpath);
70 if (len > 0 && tpath[len - 1] == '\\')
71 tpath[len - 1] = '\0';
73 *ret = strdup(tpath);
75 if (*ret == NULL)
76 return krb5_enomem(context);
78 return 0;
81 extern HINSTANCE _krb5_hInstance;
84 * Expand a %{BINDIR} token
86 * This is also used to expand a few other tokens on Windows, since
87 * most of the executable binaries end up in the same directory. The
88 * "bin" directory is considered to be the directory in which the
89 * krb5.dll is located.
91 static krb5_error_code
92 _expand_bin_dir(krb5_context context, PTYPE param, const char *postfix, char **ret)
94 TCHAR path[MAX_PATH];
95 TCHAR *lastSlash;
96 DWORD nc;
98 nc = GetModuleFileName(_krb5_hInstance, path, sizeof(path)/sizeof(path[0]));
99 if (nc == 0 ||
100 nc == sizeof(path)/sizeof(path[0])) {
101 return EINVAL;
104 lastSlash = strrchr(path, '\\');
105 if (lastSlash != NULL) {
106 TCHAR *fslash = strrchr(lastSlash, '/');
108 if (fslash != NULL)
109 lastSlash = fslash;
111 *lastSlash = '\0';
114 if (postfix) {
115 if (strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0]))
116 return EINVAL;
119 *ret = strdup(path);
120 if (*ret == NULL)
121 return krb5_enomem(context);
123 return 0;
127 * Expand a %{USERID} token
129 * The %{USERID} token expands to the string representation of the
130 * user's SID. The user account that will be used is the account
131 * corresponding to the current thread's security token. This means
132 * that:
134 * - If the current thread token has the anonymous impersonation
135 * level, the call will fail.
137 * - If the current thread is impersonating a token at
138 * SecurityIdentification level the call will fail.
141 static krb5_error_code
142 _expand_userid(krb5_context context, PTYPE param, const char *postfix, char **ret)
144 int rv = EINVAL;
145 HANDLE hThread = NULL;
146 HANDLE hToken = NULL;
147 PTOKEN_OWNER pOwner = NULL;
148 DWORD len = 0;
149 LPTSTR strSid = NULL;
151 hThread = GetCurrentThread();
153 if (!OpenThreadToken(hThread, TOKEN_QUERY,
154 FALSE, /* Open the thread token as the
155 current thread user. */
156 &hToken)) {
158 DWORD le = GetLastError();
160 if (le == ERROR_NO_TOKEN) {
161 HANDLE hProcess = GetCurrentProcess();
163 le = 0;
164 if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
165 le = GetLastError();
168 if (le != 0) {
169 if (context)
170 krb5_set_error_message(context, rv,
171 "Can't open thread token (GLE=%d)", le);
172 goto _exit;
176 if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) {
177 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
178 if (context)
179 krb5_set_error_message(context, rv,
180 "Unexpected error reading token information (GLE=%d)",
181 GetLastError());
182 goto _exit;
185 if (len == 0) {
186 if (context)
187 krb5_set_error_message(context, rv,
188 "GetTokenInformation() returned truncated buffer");
189 goto _exit;
192 pOwner = malloc(len);
193 if (pOwner == NULL) {
194 if (context)
195 krb5_set_error_message(context, rv, "Out of memory");
196 goto _exit;
198 } else {
199 if (context)
200 krb5_set_error_message(context, rv, "GetTokenInformation() returned truncated buffer");
201 goto _exit;
204 if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
205 if (context)
206 krb5_set_error_message(context, rv, "GetTokenInformation() failed. GLE=%d", GetLastError());
207 goto _exit;
210 if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
211 if (context)
212 krb5_set_error_message(context, rv, "Can't convert SID to string. GLE=%d", GetLastError());
213 goto _exit;
216 *ret = strdup(strSid);
217 if (*ret == NULL && context)
218 krb5_set_error_message(context, rv, "Out of memory");
220 rv = 0;
222 _exit:
223 if (hToken != NULL)
224 CloseHandle(hToken);
226 if (pOwner != NULL)
227 free (pOwner);
229 if (strSid != NULL)
230 LocalFree(strSid);
232 return rv;
236 * Expand a folder identified by a CSIDL
239 static krb5_error_code
240 _expand_csidl(krb5_context context, PTYPE folder, const char *postfix, char **ret)
242 TCHAR path[MAX_PATH];
243 size_t len;
245 if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path) != S_OK) {
246 if (context)
247 krb5_set_error_message(context, EINVAL, "Unable to determine folder path");
248 return EINVAL;
251 len = strlen(path);
253 if (len > 0 && path[len - 1] == '\\')
254 path[len - 1] = '\0';
256 if (postfix &&
257 strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0]))
258 return krb5_enomem(context);
260 *ret = strdup(path);
261 if (*ret == NULL)
262 return krb5_enomem(context);
263 return 0;
266 #else
268 static krb5_error_code
269 _expand_path(krb5_context context, PTYPE param, const char *postfix, char **ret)
271 *ret = strdup(postfix);
272 if (*ret == NULL)
273 return krb5_enomem(context);
274 return 0;
277 static krb5_error_code
278 _expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
280 const char *p = NULL;
282 if (!issuid())
283 p = getenv("TEMP");
285 if (p)
286 *ret = strdup(p);
287 else
288 *ret = strdup("/tmp");
289 if (*ret == NULL)
290 return krb5_enomem(context);
291 return 0;
294 static krb5_error_code
295 _expand_userid(krb5_context context, PTYPE param, const char *postfix, char **str)
297 int ret = asprintf(str, "%ld", (unsigned long)getuid());
298 if (ret < 0 || *str == NULL)
299 return krb5_enomem(context);
300 return 0;
304 #endif /* _WIN32 */
307 * Expand an extra token
310 static krb5_error_code
311 _expand_extra_token(krb5_context context, const char *value, char **ret)
313 *ret = strdup(value);
314 if (*ret == NULL)
315 return krb5_enomem(context);
316 return 0;
320 * Expand a %{null} token
322 * The expansion of a %{null} token is always the empty string.
325 static krb5_error_code
326 _expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret)
328 *ret = strdup("");
329 if (*ret == NULL)
330 return krb5_enomem(context);
331 return 0;
335 static const struct {
336 const char * tok;
337 int ftype;
338 #define FTYPE_CSIDL 0
339 #define FTYPE_SPECIAL 1
341 PTYPE param;
342 const char * postfix;
344 int (*exp_func)(krb5_context, PTYPE, const char *, char **);
346 #define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f
347 #define SPECIAL(f) SPECIALP(f, NULL)
349 } tokens[] = {
350 #ifdef _WIN32
351 #define CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl
352 #define CSIDL(C) CSIDLP(C, NULL)
354 {"APPDATA", CSIDL(CSIDL_APPDATA)}, /* Roaming application data (for current user) */
355 {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)}, /* Application data (all users) */
356 {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)}, /* Local application data (for current user) */
357 {"SYSTEM", CSIDL(CSIDL_SYSTEM)}, /* Windows System folder (e.g. %WINDIR%\System32) */
358 {"WINDOWS", CSIDL(CSIDL_WINDOWS)}, /* Windows folder */
359 {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)}, /* Per user Heimdal configuration file path */
360 {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)}, /* Common Heimdal configuration file path */
361 {"LIBDIR", SPECIAL(_expand_bin_dir)},
362 {"BINDIR", SPECIAL(_expand_bin_dir)},
363 {"LIBEXEC", SPECIAL(_expand_bin_dir)},
364 {"SBINDIR", SPECIAL(_expand_bin_dir)},
365 #else
366 {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, _expand_path},
367 {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, _expand_path},
368 {"LIBEXEC", FTYPE_SPECIAL, 0, LIBEXECDIR, _expand_path},
369 {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, _expand_path},
370 #endif
371 {"TEMP", SPECIAL(_expand_temp_folder)},
372 {"USERID", SPECIAL(_expand_userid)},
373 {"uid", SPECIAL(_expand_userid)},
374 {"null", SPECIAL(_expand_null)}
377 static krb5_error_code
378 _expand_token(krb5_context context,
379 const char *token,
380 const char *token_end,
381 char **extra_tokens,
382 char **ret)
384 size_t i;
385 char **p;
387 *ret = NULL;
389 if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
390 token_end - token <= 2) {
391 if (context)
392 krb5_set_error_message(context, EINVAL,"Invalid token.");
393 return EINVAL;
396 for (p = extra_tokens; p && p[0]; p += 2) {
397 if (strncmp(token+2, p[0], (token_end - token) - 2) == 0)
398 return _expand_extra_token(context, p[1], ret);
401 for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++) {
402 if (!strncmp(token+2, tokens[i].tok, (token_end - token) - 2))
403 return tokens[i].exp_func(context, tokens[i].param,
404 tokens[i].postfix, ret);
407 if (context)
408 krb5_set_error_message(context, EINVAL, "Invalid token.");
409 return EINVAL;
413 * Internal function to expand tokens in paths.
415 * Inputs:
417 * @context A krb5_context
418 * @path_in The path to expand tokens from
420 * Outputs:
422 * @ppath_out Path with expanded tokens (caller must free() this)
424 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
425 _krb5_expand_path_tokens(krb5_context context,
426 const char *path_in,
427 char **ppath_out)
429 return _krb5_expand_path_tokensv(context, path_in, ppath_out, NULL);
432 static void
433 free_extra_tokens(char **extra_tokens)
435 char **p;
437 for (p = extra_tokens; p && *p; p++)
438 free(*p);
439 free(extra_tokens);
443 * Internal function to expand tokens in paths.
445 * Inputs:
447 * @context A krb5_context
448 * @path_in The path to expand tokens from
449 * @ppath_out The expanded path
450 * @... Variable number of pairs of strings, the first of each
451 * being a token (e.g., "luser") and the second a string to
452 * replace it with. The list is terminated by a NULL.
454 * Outputs:
456 * @ppath_out Path with expanded tokens (caller must free() this)
458 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
459 _krb5_expand_path_tokensv(krb5_context context,
460 const char *path_in,
461 char **ppath_out, ...)
463 char *tok_begin, *tok_end, *append;
464 char **extra_tokens = NULL;
465 const char *path_left;
466 const char *s;
467 size_t nargs = 0;
468 size_t len = 0;
469 va_list ap;
471 if (path_in == NULL || *path_in == '\0') {
472 *ppath_out = strdup("");
473 return 0;
476 *ppath_out = NULL;
478 va_start(ap, ppath_out);
479 while ((s = va_arg(ap, const char *))) {
480 nargs++;
481 s = va_arg(ap, const char *);
483 va_end(ap);
484 nargs *= 2;
486 /* Get extra tokens */
487 if (nargs) {
488 size_t i;
490 extra_tokens = calloc(nargs + 1, sizeof (*extra_tokens));
491 if (extra_tokens == NULL)
492 return krb5_enomem(context);
493 va_start(ap, ppath_out);
494 for (i = 0; i < nargs; i++) {
495 s = va_arg(ap, const char *); /* token key */
496 if (s == NULL)
497 break;
498 extra_tokens[i] = strdup(s);
499 if (extra_tokens[i++] == NULL) {
500 free_extra_tokens(extra_tokens);
501 return krb5_enomem(context);
503 s = va_arg(ap, const char *); /* token value */
504 if (s == NULL)
505 s = "";
506 extra_tokens[i] = strdup(s);
507 if (extra_tokens[i] == NULL) {
508 free_extra_tokens(extra_tokens);
509 return krb5_enomem(context);
512 va_end(ap);
515 for (path_left = path_in; path_left && *path_left; ) {
517 tok_begin = strstr(path_left, "%{");
519 if (tok_begin && tok_begin != path_left) {
521 append = malloc((tok_begin - path_left) + 1);
522 if (append) {
523 memcpy(append, path_left, tok_begin - path_left);
524 append[tok_begin - path_left] = '\0';
526 path_left = tok_begin;
528 } else if (tok_begin) {
530 tok_end = strchr(tok_begin, '}');
531 if (tok_end == NULL) {
532 free_extra_tokens(extra_tokens);
533 if (*ppath_out)
534 free(*ppath_out);
535 *ppath_out = NULL;
536 if (context)
537 krb5_set_error_message(context, EINVAL, "variable missing }");
538 return EINVAL;
541 if (_expand_token(context, tok_begin, tok_end, extra_tokens,
542 &append)) {
543 free_extra_tokens(extra_tokens);
544 if (*ppath_out)
545 free(*ppath_out);
546 *ppath_out = NULL;
547 return EINVAL;
550 path_left = tok_end + 1;
551 } else {
553 append = strdup(path_left);
554 path_left = NULL;
558 if (append == NULL) {
560 free_extra_tokens(extra_tokens);
561 if (*ppath_out)
562 free(*ppath_out);
563 *ppath_out = NULL;
564 return krb5_enomem(context);
569 size_t append_len = strlen(append);
570 char * new_str = realloc(*ppath_out, len + append_len + 1);
572 if (new_str == NULL) {
573 free_extra_tokens(extra_tokens);
574 free(append);
575 if (*ppath_out)
576 free(*ppath_out);
577 *ppath_out = NULL;
578 return krb5_enomem(context);
581 *ppath_out = new_str;
582 memcpy(*ppath_out + len, append, append_len + 1);
583 len = len + append_len;
584 free(append);
588 #ifdef _WIN32
589 /* Also deal with slashes */
590 if (*ppath_out) {
591 char * c;
592 for (c = *ppath_out; *c; c++)
593 if (*c == '/')
594 *c = '\\';
596 #endif
598 free_extra_tokens(extra_tokens);
599 return 0;