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 351#endif 352 353/** 354 * Expand a %{null} token 355 * 356 * The expansion of a %{null} token is always the empty string. 357 */ 358 359static int 360_expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret) 361{ 362 *ret = strdup(""); 363 if (*ret == NULL) { 364 if (context) 365 krb5_set_error_message(context, ENOMEM, "Out of memory"); 366 return ENOMEM; 367 } 368 return 0; 369} 370 371 372static const struct token { 373 const char * tok; 374 int ftype; 375#define FTYPE_CSIDL 0 376#define FTYPE_SPECIAL 1 377 378 PTYPE param; 379 const char * postfix; 380 381 int (*exp_func)(krb5_context, PTYPE, const char *, char **); 382 383#define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f 384#define SPECIAL(f) SPECIALP(f, NULL) 385 386} tokens[] = { 387#ifdef __APPLE__ 388 {"ApplicationResources", SPECIAL(_expand_resources) }, 389#endif 390#ifdef _WIN32 391#define CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl 392#define CSIDL(C) CSIDLP(C, NULL) 393 394 {"APPDATA", CSIDL(CSIDL_APPDATA)}, /* Roaming application data (for current user) */ 395 {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)}, /* Application data (all users) */ 396 {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)}, /* Local application data (for current user) */ 397 {"SYSTEM", CSIDL(CSIDL_SYSTEM)}, /* Windows System folder (e.g. %WINDIR%\System32) */ 398 {"WINDOWS", CSIDL(CSIDL_WINDOWS)}, /* Windows folder */ 399 {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)}, /* Per user Heimdal configuration file path */ 400 {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)}, /* Common Heimdal configuration file path */ 401 {"LIBDIR", SPECIAL(_expand_bin_dir)}, 402 {"BINDIR", SPECIAL(_expand_bin_dir)}, 403 {"LIBEXEC", SPECIAL(_expand_bin_dir)}, 404 {"SBINDIR", SPECIAL(_expand_bin_dir)}, 405#else 406 {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, _expand_path}, 407 {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, _expand_path}, 408 {"LIBEXEC", FTYPE_SPECIAL, 0, LIBEXECDIR, _expand_path}, 409 {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, _expand_path}, 410#endif 411 {"TEMP", SPECIAL(_expand_temp_folder)}, 412 {"USERID", SPECIAL(_expand_userid)}, 413 {"uid", SPECIAL(_expand_userid)}, 414 {"null", SPECIAL(_expand_null)} 415}; 416 417static int 418_expand_token(krb5_context context, 419 const char *token, 420 const char *token_end, 421 char **ret) 422{ 423 size_t token_len = (token_end - token); 424 size_t i; 425 426 *ret = NULL; 427 428 if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' || 429 token_end - token <= 2) { 430 if (context) 431 krb5_set_error_message(context, EINVAL,"Invalid token: %.*s", (int)token_len, token); 432 return EINVAL; 433 } 434 435 for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++) { 436 if (!strncmp(token+2, tokens[i].tok, token_len - 2)) 437 return tokens[i].exp_func(context, tokens[i].param, 438 tokens[i].postfix, ret); 439 } 440 441 if (context) 442 krb5_set_error_message(context, EINVAL,"Invalid token: %.*s", (int)token_len, token); 443 return EINVAL; 444} 445 446KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 447_krb5_expand_path_tokens(krb5_context context, 448 const char *path_in, 449 char **ppath_out) 450{ 451 char *tok_begin, *tok_end, *append; 452 krb5_error_code ret; 453 const char *path_left; 454 size_t len = 0; 455 456 if (path_in == NULL || *path_in == '\0') { 457 *ppath_out = strdup(""); 458 return 0; 459 } 460 461 *ppath_out = NULL; 462 463 for (path_left = path_in; path_left && *path_left; ) { 464 465 tok_begin = strstr(path_left, "%{"); 466 467 if (tok_begin && tok_begin != path_left) { 468 469 append = malloc((tok_begin - path_left) + 1); 470 if (append) { 471 memcpy(append, path_left, tok_begin - path_left); 472 append[tok_begin - path_left] = '\0'; 473 } 474 path_left = tok_begin; 475 476 } else if (tok_begin) { 477 478 tok_end = strchr(tok_begin, '}'); 479 if (tok_end == NULL) { 480 if (*ppath_out) 481 free(*ppath_out); 482 *ppath_out = NULL; 483 if (context) 484 krb5_set_error_message(context, EINVAL, "variable missing }"); 485 return EINVAL; 486 } 487 488 ret = _expand_token(context, tok_begin, tok_end, &append); 489 if (ret) { 490 if (*ppath_out) 491 free(*ppath_out); 492 *ppath_out = NULL; 493 return ret; 494 } 495 496 path_left = tok_end + 1; 497 } else { 498 499 append = strdup(path_left); 500 path_left = NULL; 501 502 } 503 504 if (append == NULL) { 505 506 if (*ppath_out) 507 free(*ppath_out); 508 *ppath_out = NULL; 509 if (context) 510 krb5_set_error_message(context, ENOMEM, "malloc - out of memory"); 511 return ENOMEM; 512 513 } 514 515 { 516 size_t append_len = strlen(append); 517 char * new_str = realloc(*ppath_out, len + append_len + 1); 518 519 if (new_str == NULL) { 520 free(append); 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 *ppath_out = new_str; 530 memcpy(*ppath_out + len, append, append_len + 1); 531 len = len + append_len; 532 free(append); 533 } 534 } 535 536#ifdef _WIN32 537 /* Also deal with slashes */ 538 if (*ppath_out) { 539 char * c; 540 for (c = *ppath_out; *c; c++) 541 if (*c == '/') 542 *c = '\\'; 543 } 544#endif 545 546 return 0; 547} 548