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