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 39typedef 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 */ 56static krb5_error_code 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 83extern 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 */ 93static krb5_error_code 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 */ 143static krb5_error_code 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 241static krb5_error_code 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 270static krb5_error_code 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 279static krb5_error_code 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 296static krb5_error_code 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 312static krb5_error_code 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 327static krb5_error_code 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 337static 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 379static krb5_error_code 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 */ 426KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 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 435static void 436free_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 */ 461KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 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