1 2/*********************************************************************** 3 * Copyright (c) 2009, Secure Endpoints Inc. 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * - Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 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. 17 * 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. 30 * 31 **********************************************************************/ 32 33#include "krb5_locl.h" 34 35typedef int PTYPE; 36 37#ifdef _WIN32 38#include <shlobj.h> 39#include <sddl.h> 40 41/* 42 * Expand a %{TEMP} token 43 * 44 * The %{TEMP} token expands to the temporary path for the current 45 * user as returned by GetTempPath(). 46 * 47 * @note: Since the GetTempPath() function relies on the TMP or TEMP 48 * environment variables, this function will failover to the system 49 * temporary directory until the user profile is loaded. In addition, 50 * the returned path may or may not exist. 51 */ 52static int 53_expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret) 54{ 55 TCHAR tpath[MAX_PATH]; 56 size_t len; 57 58 if (!GetTempPath(sizeof(tpath)/sizeof(tpath[0]), tpath)) { 59 if (context) 60 krb5_set_error_message(context, EINVAL, 61 "Failed to get temporary path (GLE=%d)", 62 GetLastError()); 63 return EINVAL; 64 } 65 66 len = strlen(tpath); 67 68 if (len > 0 && tpath[len - 1] == '\\') 69 tpath[len - 1] = '\0'; 70 71 *ret = strdup(tpath); 72 73 if (*ret == NULL) { 74 if (context) 75 krb5_set_error_message(context, ENOMEM, "strdup - Out of memory"); 76 return ENOMEM; 77 } 78 79 return 0; 80} 81 82extern HINSTANCE _krb5_hInstance; 83 84/* 85 * Expand a %{BINDIR} token 86 * 87 * This is also used to expand a few other tokens on Windows, since 88 * most of the executable binaries end up in the same directory. The 89 * "bin" directory is considered to be the directory in which the 90 * krb5.dll is located. 91 */ 92static int 93_expand_bin_dir(krb5_context context, PTYPE param, const char *postfix, char **ret) 94{ 95 TCHAR path[MAX_PATH]; 96 TCHAR *lastSlash; 97 DWORD nc; 98 99 nc = GetModuleFileName(_krb5_hInstance, path, sizeof(path)/sizeof(path[0])); 100 if (nc == 0 || 101 nc == sizeof(path)/sizeof(path[0])) { 102 return EINVAL; 103 } 104 105 lastSlash = strrchr(path, '\\'); 106 if (lastSlash != NULL) { 107 TCHAR *fslash = strrchr(lastSlash, '/'); 108 109 if (fslash != NULL) 110 lastSlash = fslash; 111 112 *lastSlash = '\0'; 113 } 114 115 if (postfix) { 116 if (strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0])) 117 return EINVAL; 118 } 119 120 *ret = strdup(path); 121 if (*ret == NULL) 122 return ENOMEM; 123 124 return 0; 125} 126 127/* 128 * Expand a %{USERID} token 129 * 130 * The %{USERID} token expands to the string representation of the 131 * user's SID. The user account that will be used is the account 132 * corresponding to the current thread's security token. This means 133 * that: 134 * 135 * - If the current thread token has the anonymous impersonation 136 * level, the call will fail. 137 * 138 * - If the current thread is impersonating a token at 139 * SecurityIdentification level the call will fail. 140 * 141 */ 142static int 143_expand_userid(krb5_context context, PTYPE param, const char *postfix, char **ret) 144{ 145 int rv = EINVAL; 146 HANDLE hThread = NULL; 147 HANDLE hToken = NULL; 148 PTOKEN_OWNER pOwner = NULL; 149 DWORD len = 0; 150 LPTSTR strSid = NULL; 151 152 hThread = GetCurrentThread(); 153 154 if (!OpenThreadToken(hThread, TOKEN_QUERY, 155 FALSE, /* Open the thread token as the 156 current thread user. */ 157 &hToken)) { 158 159 DWORD le = GetLastError(); 160 161 if (le == ERROR_NO_TOKEN) { 162 HANDLE hProcess = GetCurrentProcess(); 163 164 le = 0; 165 if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) 166 le = GetLastError(); 167 } 168 169 if (le != 0) { 170 if (context) 171 krb5_set_error_message(context, rv, 172 "Can't open thread token (GLE=%d)", le); 173 goto _exit; 174 } 175 } 176 177 if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) { 178 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { 179 if (context) 180 krb5_set_error_message(context, rv, 181 "Unexpected error reading token information (GLE=%d)", 182 GetLastError()); 183 goto _exit; 184 } 185 186 if (len == 0) { 187 if (context) 188 krb5_set_error_message(context, rv, 189 "GetTokenInformation() returned truncated buffer"); 190 goto _exit; 191 } 192 193 pOwner = malloc(len); 194 if (pOwner == NULL) { 195 if (context) 196 krb5_set_error_message(context, rv, "Out of memory"); 197 goto _exit; 198 } 199 } else { 200 if (context) 201 krb5_set_error_message(context, rv, "GetTokenInformation() returned truncated buffer"); 202 goto _exit; 203 } 204 205 if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) { 206 if (context) 207 krb5_set_error_message(context, rv, "GetTokenInformation() failed. GLE=%d", GetLastError()); 208 goto _exit; 209 } 210 211 if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) { 212 if (context) 213 krb5_set_error_message(context, rv, "Can't convert SID to string. GLE=%d", GetLastError()); 214 goto _exit; 215 } 216 217 *ret = strdup(strSid); 218 if (*ret == NULL && context) 219 krb5_set_error_message(context, rv, "Out of memory"); 220 221 rv = 0; 222 223 _exit: 224 if (hToken != NULL) 225 CloseHandle(hToken); 226 227 if (pOwner != NULL) 228 free (pOwner); 229 230 if (strSid != NULL) 231 LocalFree(strSid); 232 233 return rv; 234} 235 236/* 237 * Expand a folder identified by a CSIDL 238 */ 239 240static int 241_expand_csidl(krb5_context context, PTYPE folder, const char *postfix, char **ret) 242{ 243 TCHAR path[MAX_PATH]; 244 size_t len; 245 246 if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path) != S_OK) { 247 if (context) 248 krb5_set_error_message(context, EINVAL, "Unable to determine folder path"); 249 return EINVAL; 250 } 251 252 len = strlen(path); 253 254 if (len > 0 && path[len - 1] == '\\') 255 path[len - 1] = '\0'; 256 257 if (postfix && 258 strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0])) { 259 return ENOMEM; 260 } 261 262 *ret = strdup(path); 263 if (*ret == NULL) { 264 if (context) 265 krb5_set_error_message(context, ENOMEM, "Out of memory"); 266 return ENOMEM; 267 } 268 return 0; 269} 270 271#else 272 273static int 274_expand_path(krb5_context context, PTYPE param, const char *postfix, char **ret) 275{ 276 *ret = strdup(postfix); 277 if (*ret == NULL) { 278 krb5_set_error_message(context, ENOMEM, "malloc - out of memory"); 279 return ENOMEM; 280 } 281 return 0; 282} 283 284static int 285_expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret) 286{ 287 const char *p = NULL; 288 289 if (!issuid()) 290 p = getenv("TEMP"); 291 if (p) 292 *ret = strdup(p); 293 else 294 *ret = strdup("/tmp"); 295 if (*ret == NULL) 296 return ENOMEM; 297 return 0; 298} 299 300static int 301_expand_userid(krb5_context context, PTYPE param, const char *postfix, char **str) 302{ 303 int ret = asprintf(str, "%ld", (unsigned long)getuid()); 304 if (ret < 0 || *str == NULL) 305 return ENOMEM; 306 return 0; 307} 308 309 310#endif /* _WIN32 */ 311 312#ifdef __APPLE__ 313 314#include <CoreFoundation/CoreFoundation.h> 315 316static int 317_expand_resources(krb5_context context, PTYPE param, const char *postfix, char **str) 318{ 319 char path[MAXPATHLEN]; 320 321 CFBundleRef appBundle = CFBundleGetMainBundle(); 322 if (appBundle == NULL) 323 return KRB5_CONFIG_BADFORMAT; 324 325 /* 326 * Check if there is an Info.plist, if not, then its is not a real 327 * bundle and skip 328 */ 329 CFDictionaryRef infoPlist = CFBundleGetInfoDictionary(appBundle); 330 if (infoPlist == NULL || CFDictionaryGetCount(infoPlist) == 0) 331 return KRB5_CONFIG_BADFORMAT; 332 333 CFURLRef resourcesDir = CFBundleCopyResourcesDirectoryURL(appBundle); 334 if (resourcesDir == NULL) 335 return KRB5_CONFIG_BADFORMAT; 336 337 if (!CFURLGetFileSystemRepresentation(resourcesDir, true, (UInt8 *)path, sizeof(path))) { 338 CFRelease(resourcesDir); 339 return ENOMEM; 340 } 341 342 CFRelease(resourcesDir); 343 344 *str = strdup(path); 345 if (*str == NULL) 346 return ENOMEM; 347 348 return 0; 349} 350 351static int 352_expand_env(krb5_context context, PTYPE param, const char *postfix, char **str) 353{ 354 const char *env = getenv(postfix); 355 if (env) 356 *str = strdup(env); 357 else 358 *str = strdup(""); 359 if (*str == NULL) 360 return ENOMEM; 361 return 0; 362} 363 364 365#endif 366 367/** 368 * Expand a %{null} token 369 * 370 * The expansion of a %{null} token is always the empty string. 371 */ 372 373static int 374_expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret) 375{ 376 *ret = strdup(""); 377 if (*ret == NULL) { 378 if (context) 379 krb5_set_error_message(context, ENOMEM, "Out of memory"); 380 return ENOMEM; 381 } 382 return 0; 383} 384 385 386static const struct token { 387 const char * tok; 388 int ftype; 389#define FTYPE_CSIDL 0 390#define FTYPE_SPECIAL 1 391 392 PTYPE param; 393 const char * postfix; 394 395 int (*exp_func)(krb5_context, PTYPE, const char *, char **); 396 397#define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f 398#define SPECIAL(f) SPECIALP(f, NULL) 399 400} tokens[] = { 401#ifdef __APPLE__ 402 {"ApplicationResources", SPECIAL(_expand_resources) }, 403 {"IPHONE_SIMULATOR_ROOT", SPECIALP(_expand_env, "IPHONE_SIMULATOR_ROOT") }, 404#endif 405#ifdef _WIN32 406#define CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl 407#define CSIDL(C) CSIDLP(C, NULL) 408 409 {"APPDATA", CSIDL(CSIDL_APPDATA)}, /* Roaming application data (for current user) */ 410 {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)}, /* Application data (all users) */ 411 {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)}, /* Local application data (for current user) */ 412 {"SYSTEM", CSIDL(CSIDL_SYSTEM)}, /* Windows System folder (e.g. %WINDIR%\System32) */ 413 {"WINDOWS", CSIDL(CSIDL_WINDOWS)}, /* Windows folder */ 414 {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)}, /* Per user Heimdal configuration file path */ 415 {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)}, /* Common Heimdal configuration file path */ 416 {"LIBDIR", SPECIAL(_expand_bin_dir)}, 417 {"BINDIR", SPECIAL(_expand_bin_dir)}, 418 {"LIBEXEC", SPECIAL(_expand_bin_dir)}, 419 {"SBINDIR", SPECIAL(_expand_bin_dir)}, 420#else 421 {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, _expand_path}, 422 {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, _expand_path}, 423 {"LIBEXEC", FTYPE_SPECIAL, 0, LIBEXECDIR, _expand_path}, 424 {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, _expand_path}, 425#endif 426 {"TEMP", SPECIAL(_expand_temp_folder)}, 427 {"USERID", SPECIAL(_expand_userid)}, 428 {"uid", SPECIAL(_expand_userid)}, 429 {"null", SPECIAL(_expand_null)} 430}; 431 432static int 433_expand_token(krb5_context context, 434 const char *token, 435 const char *token_end, 436 char **ret) 437{ 438 size_t token_len = (token_end - token); 439 size_t i; 440 441 *ret = NULL; 442 443 if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' || 444 token_end - token <= 2) { 445 if (context) 446 krb5_set_error_message(context, EINVAL,"Invalid token: %.*s", (int)token_len, token); 447 return EINVAL; 448 } 449 450 for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++) { 451 if (!strncmp(token+2, tokens[i].tok, token_len - 2)) 452 return tokens[i].exp_func(context, tokens[i].param, 453 tokens[i].postfix, ret); 454 } 455 456 if (context) 457 krb5_set_error_message(context, EINVAL,"Invalid token: %.*s", (int)token_len, token); 458 return EINVAL; 459} 460 461KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 462_krb5_expand_path_tokens(krb5_context context, 463 const char *path_in, 464 char **ppath_out) 465{ 466 char *tok_begin, *tok_end, *append; 467 krb5_error_code ret; 468 const char *path_left; 469 size_t len = 0; 470 471 if (path_in == NULL || *path_in == '\0') { 472 *ppath_out = strdup(""); 473 return 0; 474 } 475 476 *ppath_out = NULL; 477 478 for (path_left = path_in; path_left && *path_left; ) { 479 480 tok_begin = strstr(path_left, "%{"); 481 482 if (tok_begin && tok_begin != path_left) { 483 484 append = malloc((tok_begin - path_left) + 1); 485 if (append) { 486 memcpy(append, path_left, tok_begin - path_left); 487 append[tok_begin - path_left] = '\0'; 488 } 489 path_left = tok_begin; 490 491 } else if (tok_begin) { 492 493 tok_end = strchr(tok_begin, '}'); 494 if (tok_end == NULL) { 495 if (*ppath_out) 496 free(*ppath_out); 497 *ppath_out = NULL; 498 if (context) 499 krb5_set_error_message(context, EINVAL, "variable missing }"); 500 return EINVAL; 501 } 502 503 ret = _expand_token(context, tok_begin, tok_end, &append); 504 if (ret) { 505 if (*ppath_out) 506 free(*ppath_out); 507 *ppath_out = NULL; 508 return ret; 509 } 510 511 path_left = tok_end + 1; 512 } else { 513 514 append = strdup(path_left); 515 path_left = NULL; 516 517 } 518 519 if (append == NULL) { 520 521 if (*ppath_out) 522 free(*ppath_out); 523 *ppath_out = NULL; 524 if (context) 525 krb5_set_error_message(context, ENOMEM, "malloc - out of memory"); 526 return ENOMEM; 527 528 } 529 530 { 531 size_t append_len = strlen(append); 532 char * new_str = realloc(*ppath_out, len + append_len + 1); 533 534 if (new_str == NULL) { 535 free(append); 536 if (*ppath_out) 537 free(*ppath_out); 538 *ppath_out = NULL; 539 if (context) 540 krb5_set_error_message(context, ENOMEM, "malloc - out of memory"); 541 return ENOMEM; 542 } 543 544 *ppath_out = new_str; 545 memcpy(*ppath_out + len, append, append_len + 1); 546 len = len + append_len; 547 free(append); 548 } 549 } 550 551#ifdef _WIN32 552 /* Also deal with slashes */ 553 if (*ppath_out) { 554 char * c; 555 for (c = *ppath_out; *c; c++) 556 if (*c == '/') 557 *c = '\\'; 558 } 559#endif 560 561 return 0; 562} 563