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/**
313 * Expand a %{null} token
314 *
315 * The expansion of a %{null} token is always the empty string.
316 */
317
318static 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
331static 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
373static 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
401KRB5_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