1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/expand_path.c - Parameterized path expansion facility */
3 /*
4 * Copyright (c) 2009, Secure Endpoints Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * - Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include "k5-int.h"
34 #include "os-proto.h"
35
36 typedef int PTYPE;
37
38 #ifdef _WIN32
39 #include <shlobj.h>
40 #include <sddl.h>
41
42 /*
43 * Expand a %{TEMP} token
44 *
45 * The %{TEMP} token expands to the temporary path for the current
46 * user as returned by GetTempPath().
47 *
48 * @note: Since the GetTempPath() function relies on the TMP or TEMP
49 * environment variables, this function will failover to the system
50 * temporary directory until the user profile is loaded. In addition,
51 * the returned path may or may not exist.
52 */
53 static krb5_error_code
expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)54 expand_temp_folder(krb5_context context, PTYPE param, const char *postfix,
55 char **ret)
56 {
57 TCHAR tpath[MAX_PATH];
58 size_t len;
59
60 if (!GetTempPath(sizeof(tpath) / sizeof(tpath[0]), tpath)) {
61 k5_setmsg(context, EINVAL, "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 return ENOMEM;
75
76 return 0;
77 }
78
79 /*
80 * Expand a %{BINDIR} token
81 *
82 * This is also used to expand a few other tokens on Windows, since
83 * most of the executable binaries end up in the same directory. The
84 * "bin" directory is considered to be the directory in which the
85 * krb5.dll is located.
86 */
87 static krb5_error_code
expand_bin_dir(krb5_context context,PTYPE param,const char * postfix,char ** ret)88 expand_bin_dir(krb5_context context, PTYPE param, const char *postfix,
89 char **ret)
90 {
91 TCHAR path[MAX_PATH];
92 TCHAR *lastSlash;
93 DWORD nc;
94
95 nc = GetModuleFileName(get_lib_instance(), path,
96 sizeof(path) / sizeof(path[0]));
97 if (nc == 0 ||
98 nc == sizeof(path) / sizeof(path[0])) {
99 return EINVAL;
100 }
101
102 lastSlash = strrchr(path, '\\');
103 if (lastSlash != NULL) {
104 TCHAR *fslash = strrchr(lastSlash, '/');
105
106 if (fslash != NULL)
107 lastSlash = fslash;
108
109 *lastSlash = '\0';
110 }
111
112 if (postfix) {
113 if (strlcat(path, postfix, sizeof(path) / sizeof(path[0])) >=
114 sizeof(path) / sizeof(path[0]))
115 return EINVAL;
116 }
117
118 *ret = strdup(path);
119 if (*ret == NULL)
120 return ENOMEM;
121
122 return 0;
123 }
124
125 /*
126 * Expand a %{USERID} token
127 *
128 * The %{USERID} token expands to the string representation of the
129 * user's SID. The user account that will be used is the account
130 * corresponding to the current thread's security token. This means
131 * that:
132 *
133 * - If the current thread token has the anonymous impersonation
134 * level, the call will fail.
135 *
136 * - If the current thread is impersonating a token at
137 * SecurityIdentification level the call will fail.
138 *
139 */
140 static krb5_error_code
expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** ret)141 expand_userid(krb5_context context, PTYPE param, const char *postfix,
142 char **ret)
143 {
144 int rv = EINVAL;
145 HANDLE hThread = NULL;
146 HANDLE hToken = NULL;
147 PTOKEN_OWNER pOwner = NULL;
148 DWORD len = 0;
149 LPTSTR strSid = NULL;
150
151 hThread = GetCurrentThread();
152
153 if (!OpenThreadToken(hThread, TOKEN_QUERY,
154 FALSE, /* Open the thread token as the
155 current thread user. */
156 &hToken)) {
157
158 DWORD le = GetLastError();
159
160 if (le == ERROR_NO_TOKEN) {
161 HANDLE hProcess = GetCurrentProcess();
162
163 le = 0;
164 if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
165 le = GetLastError();
166 }
167
168 if (le != 0) {
169 k5_setmsg(context, rv, "Can't open thread token (GLE=%d)", le);
170 goto cleanup;
171 }
172 }
173
174 if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) {
175 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
176 k5_setmsg(context, rv,
177 "Unexpected error reading token information (GLE=%d)",
178 GetLastError());
179 goto cleanup;
180 }
181
182 if (len == 0) {
183 k5_setmsg(context, rv,
184 "GetTokenInformation() returned truncated buffer");
185 goto cleanup;
186 }
187
188 pOwner = malloc(len);
189 if (pOwner == NULL) {
190 rv = ENOMEM;
191 goto cleanup;
192 }
193 } else {
194 k5_setmsg(context, rv,
195 "GetTokenInformation() returned truncated buffer");
196 goto cleanup;
197 }
198
199 if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
200 k5_setmsg(context, rv,
201 "GetTokenInformation() failed. GLE=%d", GetLastError());
202 goto cleanup;
203 }
204
205 if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
206 k5_setmsg(context, rv,
207 "Can't convert SID to string. GLE=%d", GetLastError());
208 goto cleanup;
209 }
210
211 *ret = strdup(strSid);
212 if (*ret == NULL) {
213 rv = ENOMEM;
214 goto cleanup;
215 }
216
217 rv = 0;
218
219 cleanup:
220 if (hToken != NULL)
221 CloseHandle(hToken);
222
223 if (pOwner != NULL)
224 free(pOwner);
225
226 if (strSid != NULL)
227 LocalFree(strSid);
228
229 return rv;
230 }
231
232 /*
233 * Expand a folder identified by a CSIDL
234 */
235 static krb5_error_code
expand_csidl(krb5_context context,PTYPE folder,const char * postfix,char ** ret)236 expand_csidl(krb5_context context, PTYPE folder, const char *postfix,
237 char **ret)
238 {
239 TCHAR path[MAX_PATH];
240 size_t len;
241
242 if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT,
243 path) != S_OK) {
244 k5_setmsg(context, EINVAL, "Unable to determine folder path");
245 return EINVAL;
246 }
247
248 len = strlen(path);
249
250 if (len > 0 && path[len - 1] == '\\')
251 path[len - 1] = '\0';
252
253 if (postfix &&
254 strlcat(path, postfix, sizeof(path) / sizeof(path[0])) >=
255 sizeof(path)/sizeof(path[0]))
256 return ENOMEM;
257
258 *ret = strdup(path);
259 if (*ret == NULL)
260 return ENOMEM;
261 return 0;
262 }
263
264 #else /* not _WIN32 */
265 #include <pwd.h>
266
267 static krb5_error_code
expand_path(krb5_context context,PTYPE param,const char * postfix,char ** ret)268 expand_path(krb5_context context, PTYPE param, const char *postfix, char **ret)
269 {
270 *ret = strdup(postfix);
271 if (*ret == NULL)
272 return ENOMEM;
273 return 0;
274 }
275
276 static krb5_error_code
expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)277 expand_temp_folder(krb5_context context, PTYPE param, const char *postfix,
278 char **ret)
279 {
280 const char *p = NULL;
281
282 if (context == NULL || !context->profile_secure)
283 p = secure_getenv("TMPDIR");
284 *ret = strdup((p != NULL) ? p : "/tmp");
285 if (*ret == NULL)
286 return ENOMEM;
287 return 0;
288 }
289
290 static krb5_error_code
expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** str)291 expand_userid(krb5_context context, PTYPE param, const char *postfix,
292 char **str)
293 {
294 if (asprintf(str, "%lu", (unsigned long)getuid()) < 0)
295 return ENOMEM;
296 return 0;
297 }
298
299 static krb5_error_code
expand_euid(krb5_context context,PTYPE param,const char * postfix,char ** str)300 expand_euid(krb5_context context, PTYPE param, const char *postfix, char **str)
301 {
302 if (asprintf(str, "%lu", (unsigned long)geteuid()) < 0)
303 return ENOMEM;
304 return 0;
305 }
306
307 static krb5_error_code
expand_username(krb5_context context,PTYPE param,const char * postfix,char ** str)308 expand_username(krb5_context context, PTYPE param, const char *postfix,
309 char **str)
310 {
311 uid_t euid = geteuid();
312 struct passwd *pw, pwx;
313 char pwbuf[BUFSIZ];
314
315 if (k5_getpwuid_r(euid, &pwx, pwbuf, sizeof(pwbuf), &pw) != 0) {
316 k5_setmsg(context, ENOENT, _("Can't find username for uid %lu"),
317 (unsigned long)euid);
318 return ENOENT;
319 }
320 *str = strdup(pw->pw_name);
321 if (*str == NULL)
322 return ENOMEM;
323 return 0;
324 }
325
326 #endif /* not _WIN32 */
327
328 /*
329 * Expand an extra token
330 */
331 static krb5_error_code
expand_extra_token(krb5_context context,const char * value,char ** ret)332 expand_extra_token(krb5_context context, const char *value, char **ret)
333 {
334 *ret = strdup(value);
335 if (*ret == NULL)
336 return ENOMEM;
337 return 0;
338 }
339
340 /*
341 * Expand a %{null} token
342 *
343 * The expansion of a %{null} token is always the empty string.
344 */
345 static krb5_error_code
expand_null(krb5_context context,PTYPE param,const char * postfix,char ** ret)346 expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret)
347 {
348 *ret = strdup("");
349 if (*ret == NULL)
350 return ENOMEM;
351 return 0;
352 }
353
354 static const struct {
355 const char *tok;
356 PTYPE param;
357 const char *postfix;
358 int (*exp_func)(krb5_context, PTYPE, const char *, char **);
359 } tokens[] = {
360 #ifdef _WIN32
361 /* Roaming application data (for current user) */
362 {"APPDATA", CSIDL_APPDATA, NULL, expand_csidl},
363 /* Application data (all users) */
364 {"COMMON_APPDATA", CSIDL_COMMON_APPDATA, NULL, expand_csidl},
365 /* Local application data (for current user) */
366 {"LOCAL_APPDATA", CSIDL_LOCAL_APPDATA, NULL, expand_csidl},
367 /* Windows System folder (e.g. %WINDIR%\System32) */
368 {"SYSTEM", CSIDL_SYSTEM, NULL, expand_csidl},
369 /* Windows folder */
370 {"WINDOWS", CSIDL_WINDOWS, NULL, expand_csidl},
371 /* Per user MIT krb5 configuration file directory */
372 {"USERCONFIG", CSIDL_APPDATA, "\\MIT\\Kerberos5",
373 expand_csidl},
374 /* Common MIT krb5 configuration file directory */
375 {"COMMONCONFIG", CSIDL_COMMON_APPDATA, "\\MIT\\Kerberos5",
376 expand_csidl},
377 {"LIBDIR", 0, NULL, expand_bin_dir},
378 {"BINDIR", 0, NULL, expand_bin_dir},
379 {"SBINDIR", 0, NULL, expand_bin_dir},
380 {"euid", 0, NULL, expand_userid},
381 #else
382 {"LIBDIR", 0, LIBDIR, expand_path},
383 {"BINDIR", 0, BINDIR, expand_path},
384 {"SBINDIR", 0, SBINDIR, expand_path},
385 {"euid", 0, NULL, expand_euid},
386 {"username", 0, NULL, expand_username},
387 #endif
388 {"TEMP", 0, NULL, expand_temp_folder},
389 {"USERID", 0, NULL, expand_userid},
390 {"uid", 0, NULL, expand_userid},
391 {"null", 0, NULL, expand_null}
392 };
393
394 static krb5_error_code
expand_token(krb5_context context,const char * token,const char * token_end,char ** extra_tokens,char ** ret)395 expand_token(krb5_context context, const char *token, const char *token_end,
396 char **extra_tokens, char **ret)
397 {
398 size_t i;
399 char **p;
400
401 *ret = NULL;
402
403 if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
404 token_end - token <= 2) {
405 k5_setmsg(context, EINVAL, _("Invalid token"));
406 return EINVAL;
407 }
408
409 for (p = extra_tokens; p != NULL && *p != NULL; p += 2) {
410 if (strncmp(token + 2, *p, (token_end - token) - 2) == 0)
411 return expand_extra_token(context, p[1], ret);
412 }
413
414 for (i = 0; i < sizeof(tokens) / sizeof(tokens[0]); i++) {
415 if (!strncmp(token + 2, tokens[i].tok, (token_end - token) - 2)) {
416 return tokens[i].exp_func(context, tokens[i].param,
417 tokens[i].postfix, ret);
418 }
419 }
420
421 k5_setmsg(context, EINVAL, _("Invalid token"));
422 return EINVAL;
423 }
424
425 /*
426 * Expand tokens in path_in to produce *path_out. The caller should free
427 * *path_out with free().
428 */
429 krb5_error_code
k5_expand_path_tokens(krb5_context context,const char * path_in,char ** path_out)430 k5_expand_path_tokens(krb5_context context, const char *path_in,
431 char **path_out)
432 {
433 return k5_expand_path_tokens_extra(context, path_in, path_out, NULL);
434 }
435
436 static void
free_extra_tokens(char ** extra_tokens)437 free_extra_tokens(char **extra_tokens)
438 {
439 char **p;
440
441 for (p = extra_tokens; p != NULL && *p != NULL; p++)
442 free(*p);
443 free(extra_tokens);
444 }
445
446 /*
447 * Expand tokens in path_in to produce *path_out. Arguments after path_out are
448 * pairs of extra token names and replacement values, terminated by a NULL.
449 * The caller should free *path_out with free().
450 */
451 krb5_error_code
k5_expand_path_tokens_extra(krb5_context context,const char * path_in,char ** path_out,...)452 k5_expand_path_tokens_extra(krb5_context context, const char *path_in,
453 char **path_out, ...)
454 {
455 krb5_error_code ret;
456 struct k5buf buf;
457 char *tok_begin, *tok_end, *tok_val, **extra_tokens = NULL, *path;
458 const char *path_left;
459 size_t nargs = 0, i;
460 va_list ap;
461
462 *path_out = NULL;
463
464 k5_buf_init_dynamic(&buf);
465
466 /* Count extra tokens. */
467 va_start(ap, path_out);
468 while (va_arg(ap, const char *) != NULL)
469 nargs++;
470 va_end(ap);
471 if (nargs % 2 != 0)
472 return EINVAL;
473
474 /* Get extra tokens. */
475 if (nargs > 0) {
476 extra_tokens = k5calloc(nargs + 1, sizeof(char *), &ret);
477 if (extra_tokens == NULL)
478 goto cleanup;
479 va_start(ap, path_out);
480 for (i = 0; i < nargs; i++) {
481 extra_tokens[i] = strdup(va_arg(ap, const char *));
482 if (extra_tokens[i] == NULL) {
483 ret = ENOMEM;
484 va_end(ap);
485 goto cleanup;
486 }
487 }
488 va_end(ap);
489 }
490
491 path_left = path_in;
492 while (TRUE) {
493 /* Find the next token in path_left and add the literal text up to it.
494 * If there are no more tokens, we can finish up. */
495 tok_begin = strstr(path_left, "%{");
496 if (tok_begin == NULL) {
497 k5_buf_add(&buf, path_left);
498 break;
499 }
500 k5_buf_add_len(&buf, path_left, tok_begin - path_left);
501
502 /* Find the end of this token. */
503 tok_end = strchr(tok_begin, '}');
504 if (tok_end == NULL) {
505 ret = EINVAL;
506 k5_setmsg(context, ret, _("variable missing }"));
507 goto cleanup;
508 }
509
510 /* Expand this token and add its value. */
511 ret = expand_token(context, tok_begin, tok_end, extra_tokens,
512 &tok_val);
513 if (ret)
514 goto cleanup;
515 k5_buf_add(&buf, tok_val);
516 free(tok_val);
517 path_left = tok_end + 1;
518 }
519
520 path = k5_buf_cstring(&buf);
521 if (path == NULL) {
522 ret = ENOMEM;
523 goto cleanup;
524 }
525
526 #ifdef _WIN32
527 /* Also deal with slashes. */
528 {
529 char *p;
530 for (p = path; *p != '\0'; p++) {
531 if (*p == '/')
532 *p = '\\';
533 }
534 }
535 #endif
536 *path_out = path;
537 memset(&buf, 0, sizeof(buf));
538 ret = 0;
539
540 cleanup:
541 k5_buf_free(&buf);
542 free_extra_tokens(extra_tokens);
543 return ret;
544 }
545