1 /* $NetBSD: expand_path.c,v 1.2 2017/01/28 21:31:49 christos Exp $ */
2
3
4 /***********************************************************************
5 * Copyright (c) 2009, Secure Endpoints Inc.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * - Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31 * OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 **********************************************************************/
34
35 #include "krb5_locl.h"
36
37 #include <stdarg.h>
38
39 typedef int PTYPE;
40
41 #ifdef _WIN32
42 #include <shlobj.h>
43 #include <sddl.h>
44
45 /*
46 * Expand a %{TEMP} token
47 *
48 * The %{TEMP} token expands to the temporary path for the current
49 * user as returned by GetTempPath().
50 *
51 * @note: Since the GetTempPath() function relies on the TMP or TEMP
52 * environment variables, this function will failover to the system
53 * temporary directory until the user profile is loaded. In addition,
54 * the returned path may or may not exist.
55 */
56 static krb5_error_code
_expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)57 _expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
58 {
59 TCHAR tpath[MAX_PATH];
60 size_t len;
61
62 if (!GetTempPath(sizeof(tpath)/sizeof(tpath[0]), tpath)) {
63 if (context)
64 krb5_set_error_message(context, EINVAL,
65 "Failed to get temporary path (GLE=%d)",
66 GetLastError());
67 return EINVAL;
68 }
69
70 len = strlen(tpath);
71
72 if (len > 0 && tpath[len - 1] == '\\')
73 tpath[len - 1] = '\0';
74
75 *ret = strdup(tpath);
76
77 if (*ret == NULL)
78 return krb5_enomem(context);
79
80 return 0;
81 }
82
83 extern HINSTANCE _krb5_hInstance;
84
85 /*
86 * Expand a %{BINDIR} token
87 *
88 * This is also used to expand a few other tokens on Windows, since
89 * most of the executable binaries end up in the same directory. The
90 * "bin" directory is considered to be the directory in which the
91 * krb5.dll is located.
92 */
93 static krb5_error_code
_expand_bin_dir(krb5_context context,PTYPE param,const char * postfix,char ** ret)94 _expand_bin_dir(krb5_context context, PTYPE param, const char *postfix, char **ret)
95 {
96 TCHAR path[MAX_PATH];
97 TCHAR *lastSlash;
98 DWORD nc;
99
100 nc = GetModuleFileName(_krb5_hInstance, path, sizeof(path)/sizeof(path[0]));
101 if (nc == 0 ||
102 nc == sizeof(path)/sizeof(path[0])) {
103 return EINVAL;
104 }
105
106 lastSlash = strrchr(path, '\\');
107 if (lastSlash != NULL) {
108 TCHAR *fslash = strrchr(lastSlash, '/');
109
110 if (fslash != NULL)
111 lastSlash = fslash;
112
113 *lastSlash = '\0';
114 }
115
116 if (postfix) {
117 if (strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0]))
118 return EINVAL;
119 }
120
121 *ret = strdup(path);
122 if (*ret == NULL)
123 return krb5_enomem(context);
124
125 return 0;
126 }
127
128 /*
129 * Expand a %{USERID} token
130 *
131 * The %{USERID} token expands to the string representation of the
132 * user's SID. The user account that will be used is the account
133 * corresponding to the current thread's security token. This means
134 * that:
135 *
136 * - If the current thread token has the anonymous impersonation
137 * level, the call will fail.
138 *
139 * - If the current thread is impersonating a token at
140 * SecurityIdentification level the call will fail.
141 *
142 */
143 static krb5_error_code
_expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** ret)144 _expand_userid(krb5_context context, PTYPE param, const char *postfix, char **ret)
145 {
146 int rv = EINVAL;
147 HANDLE hThread = NULL;
148 HANDLE hToken = NULL;
149 PTOKEN_OWNER pOwner = NULL;
150 DWORD len = 0;
151 LPTSTR strSid = NULL;
152
153 hThread = GetCurrentThread();
154
155 if (!OpenThreadToken(hThread, TOKEN_QUERY,
156 FALSE, /* Open the thread token as the
157 current thread user. */
158 &hToken)) {
159
160 DWORD le = GetLastError();
161
162 if (le == ERROR_NO_TOKEN) {
163 HANDLE hProcess = GetCurrentProcess();
164
165 le = 0;
166 if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
167 le = GetLastError();
168 }
169
170 if (le != 0) {
171 if (context)
172 krb5_set_error_message(context, rv,
173 "Can't open thread token (GLE=%d)", le);
174 goto _exit;
175 }
176 }
177
178 if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) {
179 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
180 if (context)
181 krb5_set_error_message(context, rv,
182 "Unexpected error reading token information (GLE=%d)",
183 GetLastError());
184 goto _exit;
185 }
186
187 if (len == 0) {
188 if (context)
189 krb5_set_error_message(context, rv,
190 "GetTokenInformation() returned truncated buffer");
191 goto _exit;
192 }
193
194 pOwner = malloc(len);
195 if (pOwner == NULL) {
196 if (context)
197 krb5_set_error_message(context, rv, "Out of memory");
198 goto _exit;
199 }
200 } else {
201 if (context)
202 krb5_set_error_message(context, rv, "GetTokenInformation() returned truncated buffer");
203 goto _exit;
204 }
205
206 if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
207 if (context)
208 krb5_set_error_message(context, rv, "GetTokenInformation() failed. GLE=%d", GetLastError());
209 goto _exit;
210 }
211
212 if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
213 if (context)
214 krb5_set_error_message(context, rv, "Can't convert SID to string. GLE=%d", GetLastError());
215 goto _exit;
216 }
217
218 *ret = strdup(strSid);
219 if (*ret == NULL && context)
220 krb5_set_error_message(context, rv, "Out of memory");
221
222 rv = 0;
223
224 _exit:
225 if (hToken != NULL)
226 CloseHandle(hToken);
227
228 if (pOwner != NULL)
229 free (pOwner);
230
231 if (strSid != NULL)
232 LocalFree(strSid);
233
234 return rv;
235 }
236
237 /*
238 * Expand a folder identified by a CSIDL
239 */
240
241 static krb5_error_code
_expand_csidl(krb5_context context,PTYPE folder,const char * postfix,char ** ret)242 _expand_csidl(krb5_context context, PTYPE folder, const char *postfix, char **ret)
243 {
244 TCHAR path[MAX_PATH];
245 size_t len;
246
247 if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path) != S_OK) {
248 if (context)
249 krb5_set_error_message(context, EINVAL, "Unable to determine folder path");
250 return EINVAL;
251 }
252
253 len = strlen(path);
254
255 if (len > 0 && path[len - 1] == '\\')
256 path[len - 1] = '\0';
257
258 if (postfix &&
259 strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0]))
260 return krb5_enomem(context);
261
262 *ret = strdup(path);
263 if (*ret == NULL)
264 return krb5_enomem(context);
265 return 0;
266 }
267
268 #else
269
270 static krb5_error_code
_expand_path(krb5_context context,PTYPE param,const char * postfix,char ** ret)271 _expand_path(krb5_context context, PTYPE param, const char *postfix, char **ret)
272 {
273 *ret = strdup(postfix);
274 if (*ret == NULL)
275 return krb5_enomem(context);
276 return 0;
277 }
278
279 static krb5_error_code
_expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)280 _expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
281 {
282 const char *p = NULL;
283
284 if (!issuid())
285 p = getenv("TEMP");
286
287 if (p)
288 *ret = strdup(p);
289 else
290 *ret = strdup("/tmp");
291 if (*ret == NULL)
292 return krb5_enomem(context);
293 return 0;
294 }
295
296 static krb5_error_code
_expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** str)297 _expand_userid(krb5_context context, PTYPE param, const char *postfix, char **str)
298 {
299 int ret = asprintf(str, "%ld", (unsigned long)getuid());
300 if (ret < 0 || *str == NULL)
301 return krb5_enomem(context);
302 return 0;
303 }
304
305
306 #endif /* _WIN32 */
307
308 /**
309 * Expand an extra token
310 */
311
312 static krb5_error_code
_expand_extra_token(krb5_context context,const char * value,char ** ret)313 _expand_extra_token(krb5_context context, const char *value, char **ret)
314 {
315 *ret = strdup(value);
316 if (*ret == NULL)
317 return krb5_enomem(context);
318 return 0;
319 }
320
321 /**
322 * Expand a %{null} token
323 *
324 * The expansion of a %{null} token is always the empty string.
325 */
326
327 static krb5_error_code
_expand_null(krb5_context context,PTYPE param,const char * postfix,char ** ret)328 _expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret)
329 {
330 *ret = strdup("");
331 if (*ret == NULL)
332 return krb5_enomem(context);
333 return 0;
334 }
335
336
337 static const struct {
338 const char * tok;
339 int ftype;
340 #define FTYPE_CSIDL 0
341 #define FTYPE_SPECIAL 1
342
343 PTYPE param;
344 const char * postfix;
345
346 int (*exp_func)(krb5_context, PTYPE, const char *, char **);
347
348 #define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f
349 #define SPECIAL(f) SPECIALP(f, NULL)
350
351 } tokens[] = {
352 #ifdef _WIN32
353 #define CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl
354 #define CSIDL(C) CSIDLP(C, NULL)
355
356 {"APPDATA", CSIDL(CSIDL_APPDATA)}, /* Roaming application data (for current user) */
357 {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)}, /* Application data (all users) */
358 {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)}, /* Local application data (for current user) */
359 {"SYSTEM", CSIDL(CSIDL_SYSTEM)}, /* Windows System folder (e.g. %WINDIR%\System32) */
360 {"WINDOWS", CSIDL(CSIDL_WINDOWS)}, /* Windows folder */
361 {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)}, /* Per user Heimdal configuration file path */
362 {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)}, /* Common Heimdal configuration file path */
363 {"LIBDIR", SPECIAL(_expand_bin_dir)},
364 {"BINDIR", SPECIAL(_expand_bin_dir)},
365 {"LIBEXEC", SPECIAL(_expand_bin_dir)},
366 {"SBINDIR", SPECIAL(_expand_bin_dir)},
367 #else
368 {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, _expand_path},
369 {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, _expand_path},
370 {"LIBEXEC", FTYPE_SPECIAL, 0, LIBEXECDIR, _expand_path},
371 {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, _expand_path},
372 #endif
373 {"TEMP", SPECIAL(_expand_temp_folder)},
374 {"USERID", SPECIAL(_expand_userid)},
375 {"uid", SPECIAL(_expand_userid)},
376 {"null", SPECIAL(_expand_null)}
377 };
378
379 static krb5_error_code
_expand_token(krb5_context context,const char * token,const char * token_end,char ** extra_tokens,char ** ret)380 _expand_token(krb5_context context,
381 const char *token,
382 const char *token_end,
383 char **extra_tokens,
384 char **ret)
385 {
386 size_t i;
387 char **p;
388
389 *ret = NULL;
390
391 if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
392 token_end - token <= 2) {
393 if (context)
394 krb5_set_error_message(context, EINVAL,"Invalid token.");
395 return EINVAL;
396 }
397
398 for (p = extra_tokens; p && p[0]; p += 2) {
399 if (strncmp(token+2, p[0], (token_end - token) - 2) == 0)
400 return _expand_extra_token(context, p[1], ret);
401 }
402
403 for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++) {
404 if (!strncmp(token+2, tokens[i].tok, (token_end - token) - 2))
405 return tokens[i].exp_func(context, tokens[i].param,
406 tokens[i].postfix, ret);
407 }
408
409 if (context)
410 krb5_set_error_message(context, EINVAL, "Invalid token.");
411 return EINVAL;
412 }
413
414 /**
415 * Internal function to expand tokens in paths.
416 *
417 * Inputs:
418 *
419 * @context A krb5_context
420 * @path_in The path to expand tokens from
421 *
422 * Outputs:
423 *
424 * @ppath_out Path with expanded tokens (caller must free() this)
425 */
426 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_expand_path_tokens(krb5_context context,const char * path_in,int filepath,char ** ppath_out)427 _krb5_expand_path_tokens(krb5_context context,
428 const char *path_in,
429 int filepath,
430 char **ppath_out)
431 {
432 return _krb5_expand_path_tokensv(context, path_in, filepath, ppath_out, NULL);
433 }
434
435 static void
free_extra_tokens(char ** extra_tokens)436 free_extra_tokens(char **extra_tokens)
437 {
438 char **p;
439
440 for (p = extra_tokens; p && *p; p++)
441 free(*p);
442 free(extra_tokens);
443 }
444
445 /**
446 * Internal function to expand tokens in paths.
447 *
448 * Inputs:
449 *
450 * @context A krb5_context
451 * @path_in The path to expand tokens from
452 * @ppath_out The expanded path
453 * @... Variable number of pairs of strings, the first of each
454 * being a token (e.g., "luser") and the second a string to
455 * replace it with. The list is terminated by a NULL.
456 *
457 * Outputs:
458 *
459 * @ppath_out Path with expanded tokens (caller must free() this)
460 */
461 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_expand_path_tokensv(krb5_context context,const char * path_in,int filepath,char ** ppath_out,...)462 _krb5_expand_path_tokensv(krb5_context context,
463 const char *path_in,
464 int filepath,
465 char **ppath_out, ...)
466 {
467 char *tok_begin, *tok_end, *append;
468 char **extra_tokens = NULL;
469 const char *path_left;
470 size_t nargs = 0;
471 size_t len = 0;
472 va_list ap;
473
474 if (path_in == NULL || *path_in == '\0') {
475 *ppath_out = strdup("");
476 return 0;
477 }
478
479 *ppath_out = NULL;
480
481 va_start(ap, ppath_out);
482 while (va_arg(ap, const char *)) {
483 nargs++;
484 va_arg(ap, const char *);
485 }
486 va_end(ap);
487 nargs *= 2;
488
489 /* Get extra tokens */
490 if (nargs) {
491 size_t i;
492
493 extra_tokens = calloc(nargs + 1, sizeof (*extra_tokens));
494 if (extra_tokens == NULL)
495 return krb5_enomem(context);
496 va_start(ap, ppath_out);
497 for (i = 0; i < nargs; i++) {
498 const char *s = va_arg(ap, const char *); /* token key */
499 if (s == NULL)
500 break;
501 extra_tokens[i] = strdup(s);
502 if (extra_tokens[i++] == NULL) {
503 va_end(ap);
504 free_extra_tokens(extra_tokens);
505 return krb5_enomem(context);
506 }
507 s = va_arg(ap, const char *); /* token value */
508 if (s == NULL)
509 s = "";
510 extra_tokens[i] = strdup(s);
511 if (extra_tokens[i] == NULL) {
512 va_end(ap);
513 free_extra_tokens(extra_tokens);
514 return krb5_enomem(context);
515 }
516 }
517 va_end(ap);
518 }
519
520 for (path_left = path_in; path_left && *path_left; ) {
521
522 tok_begin = strstr(path_left, "%{");
523
524 if (tok_begin && tok_begin != path_left) {
525
526 append = malloc((tok_begin - path_left) + 1);
527 if (append) {
528 memcpy(append, path_left, tok_begin - path_left);
529 append[tok_begin - path_left] = '\0';
530 }
531 path_left = tok_begin;
532
533 } else if (tok_begin) {
534
535 tok_end = strchr(tok_begin, '}');
536 if (tok_end == NULL) {
537 free_extra_tokens(extra_tokens);
538 if (*ppath_out)
539 free(*ppath_out);
540 *ppath_out = NULL;
541 if (context)
542 krb5_set_error_message(context, EINVAL, "variable missing }");
543 return EINVAL;
544 }
545
546 if (_expand_token(context, tok_begin, tok_end, extra_tokens,
547 &append)) {
548 free_extra_tokens(extra_tokens);
549 if (*ppath_out)
550 free(*ppath_out);
551 *ppath_out = NULL;
552 return EINVAL;
553 }
554
555 path_left = tok_end + 1;
556 } else {
557
558 append = strdup(path_left);
559 path_left = NULL;
560
561 }
562
563 if (append == NULL) {
564
565 free_extra_tokens(extra_tokens);
566 if (*ppath_out)
567 free(*ppath_out);
568 *ppath_out = NULL;
569 return krb5_enomem(context);
570
571 }
572
573 {
574 size_t append_len = strlen(append);
575 char * new_str = realloc(*ppath_out, len + append_len + 1);
576
577 if (new_str == NULL) {
578 free_extra_tokens(extra_tokens);
579 free(append);
580 if (*ppath_out)
581 free(*ppath_out);
582 *ppath_out = NULL;
583 return krb5_enomem(context);
584 }
585
586 *ppath_out = new_str;
587 memcpy(*ppath_out + len, append, append_len + 1);
588 len = len + append_len;
589 free(append);
590 }
591 }
592
593 #ifdef _WIN32
594 /* Also deal with slashes */
595 if (filepath && *ppath_out) {
596 char * c;
597
598 for (c = *ppath_out; *c; c++)
599 if (*c == '/')
600 *c = '\\';
601 }
602 #endif
603
604 free_extra_tokens(extra_tokens);
605 return 0;
606 }
607