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 35 typedef 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 */ 52 static 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 82 extern 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 */ 92 static 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 */ 142 static 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 240 static 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 273 static 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 284 static 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 300 static 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 /** 313 * Expand a %{null} token 314 * 315 * The expansion of a %{null} token is always the empty string. 316 */ 317 318 static int 319 _expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret) 320 { 321 *ret = strdup(""); 322 if (*ret == NULL) { 323 if (context) 324 krb5_set_error_message(context, ENOMEM, "Out of memory"); 325 return ENOMEM; 326 } 327 return 0; 328 } 329 330 331 static const struct token { 332 const char * tok; 333 int ftype; 334 #define FTYPE_CSIDL 0 335 #define FTYPE_SPECIAL 1 336 337 PTYPE param; 338 const char * postfix; 339 340 int (*exp_func)(krb5_context, PTYPE, const char *, char **); 341 342 #define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f 343 #define SPECIAL(f) SPECIALP(f, NULL) 344 345 } tokens[] = { 346 #ifdef _WIN32 347 #define CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl 348 #define CSIDL(C) CSIDLP(C, NULL) 349 350 {"APPDATA", CSIDL(CSIDL_APPDATA)}, /* Roaming application data (for current user) */ 351 {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)}, /* Application data (all users) */ 352 {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)}, /* Local application data (for current user) */ 353 {"SYSTEM", CSIDL(CSIDL_SYSTEM)}, /* Windows System folder (e.g. %WINDIR%\System32) */ 354 {"WINDOWS", CSIDL(CSIDL_WINDOWS)}, /* Windows folder */ 355 {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)}, /* Per user Heimdal configuration file path */ 356 {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)}, /* Common Heimdal configuration file path */ 357 {"LIBDIR", SPECIAL(_expand_bin_dir)}, 358 {"BINDIR", SPECIAL(_expand_bin_dir)}, 359 {"LIBEXEC", SPECIAL(_expand_bin_dir)}, 360 {"SBINDIR", SPECIAL(_expand_bin_dir)}, 361 #else 362 {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, _expand_path}, 363 {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, _expand_path}, 364 {"LIBEXEC", FTYPE_SPECIAL, 0, LIBEXECDIR, _expand_path}, 365 {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, _expand_path}, 366 #endif 367 {"TEMP", SPECIAL(_expand_temp_folder)}, 368 {"USERID", SPECIAL(_expand_userid)}, 369 {"uid", SPECIAL(_expand_userid)}, 370 {"null", SPECIAL(_expand_null)} 371 }; 372 373 static int 374 _expand_token(krb5_context context, 375 const char *token, 376 const char *token_end, 377 char **ret) 378 { 379 size_t i; 380 381 *ret = NULL; 382 383 if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' || 384 token_end - token <= 2) { 385 if (context) 386 krb5_set_error_message(context, EINVAL,"Invalid token."); 387 return EINVAL; 388 } 389 390 for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++) { 391 if (!strncmp(token+2, tokens[i].tok, (token_end - token) - 2)) 392 return tokens[i].exp_func(context, tokens[i].param, 393 tokens[i].postfix, ret); 394 } 395 396 if (context) 397 krb5_set_error_message(context, EINVAL, "Invalid token."); 398 return EINVAL; 399 } 400 401 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 402 _krb5_expand_path_tokens(krb5_context context, 403 const char *path_in, 404 char **ppath_out) 405 { 406 char *tok_begin, *tok_end, *append; 407 const char *path_left; 408 size_t len = 0; 409 410 if (path_in == NULL || *path_in == '\0') { 411 *ppath_out = strdup(""); 412 return 0; 413 } 414 415 *ppath_out = NULL; 416 417 for (path_left = path_in; path_left && *path_left; ) { 418 419 tok_begin = strstr(path_left, "%{"); 420 421 if (tok_begin && tok_begin != path_left) { 422 423 append = malloc((tok_begin - path_left) + 1); 424 if (append) { 425 memcpy(append, path_left, tok_begin - path_left); 426 append[tok_begin - path_left] = '\0'; 427 } 428 path_left = tok_begin; 429 430 } else if (tok_begin) { 431 432 tok_end = strchr(tok_begin, '}'); 433 if (tok_end == NULL) { 434 if (*ppath_out) 435 free(*ppath_out); 436 *ppath_out = NULL; 437 if (context) 438 krb5_set_error_message(context, EINVAL, "variable missing }"); 439 return EINVAL; 440 } 441 442 if (_expand_token(context, tok_begin, tok_end, &append)) { 443 if (*ppath_out) 444 free(*ppath_out); 445 *ppath_out = NULL; 446 return EINVAL; 447 } 448 449 path_left = tok_end + 1; 450 } else { 451 452 append = strdup(path_left); 453 path_left = NULL; 454 455 } 456 457 if (append == NULL) { 458 459 if (*ppath_out) 460 free(*ppath_out); 461 *ppath_out = NULL; 462 if (context) 463 krb5_set_error_message(context, ENOMEM, "malloc - out of memory"); 464 return ENOMEM; 465 466 } 467 468 { 469 size_t append_len = strlen(append); 470 char * new_str = realloc(*ppath_out, len + append_len + 1); 471 472 if (new_str == NULL) { 473 free(append); 474 if (*ppath_out) 475 free(*ppath_out); 476 *ppath_out = NULL; 477 if (context) 478 krb5_set_error_message(context, ENOMEM, "malloc - out of memory"); 479 return ENOMEM; 480 } 481 482 *ppath_out = new_str; 483 memcpy(*ppath_out + len, append, append_len + 1); 484 len = len + append_len; 485 free(append); 486 } 487 } 488 489 #ifdef _WIN32 490 /* Also deal with slashes */ 491 if (*ppath_out) { 492 char * c; 493 for (c = *ppath_out; *c; c++) 494 if (*c == '/') 495 *c = '\\'; 496 } 497 #endif 498 499 return 0; 500 } 501