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