1226031Sstas
2226031Sstas/***********************************************************************
3226031Sstas * Copyright (c) 2009, Secure Endpoints Inc.
4226031Sstas * All rights reserved.
5226031Sstas *
6226031Sstas * Redistribution and use in source and binary forms, with or without
7226031Sstas * modification, are permitted provided that the following conditions
8226031Sstas * are met:
9226031Sstas *
10226031Sstas * - Redistributions of source code must retain the above copyright
11226031Sstas *   notice, this list of conditions and the following disclaimer.
12226031Sstas *
13226031Sstas * - Redistributions in binary form must reproduce the above copyright
14226031Sstas *   notice, this list of conditions and the following disclaimer in
15226031Sstas *   the documentation and/or other materials provided with the
16226031Sstas *   distribution.
17226031Sstas *
18226031Sstas * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19226031Sstas * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20226031Sstas * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21226031Sstas * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22226031Sstas * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23226031Sstas * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24226031Sstas * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25226031Sstas * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26226031Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27226031Sstas * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28226031Sstas * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29226031Sstas * OF THE POSSIBILITY OF SUCH DAMAGE.
30226031Sstas *
31226031Sstas **********************************************************************/
32226031Sstas
33226031Sstas#include "krb5_locl.h"
34226031Sstas
35226031Sstastypedef int PTYPE;
36226031Sstas
37226031Sstas#ifdef _WIN32
38226031Sstas#include <shlobj.h>
39226031Sstas#include <sddl.h>
40226031Sstas
41226031Sstas/*
42226031Sstas * Expand a %{TEMP} token
43226031Sstas *
44226031Sstas * The %{TEMP} token expands to the temporary path for the current
45226031Sstas * user as returned by GetTempPath().
46226031Sstas *
47226031Sstas * @note: Since the GetTempPath() function relies on the TMP or TEMP
48226031Sstas * environment variables, this function will failover to the system
49226031Sstas * temporary directory until the user profile is loaded.  In addition,
50226031Sstas * the returned path may or may not exist.
51226031Sstas */
52226031Sstasstatic int
53226031Sstas_expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
54226031Sstas{
55226031Sstas    TCHAR tpath[MAX_PATH];
56226031Sstas    size_t len;
57226031Sstas
58226031Sstas    if (!GetTempPath(sizeof(tpath)/sizeof(tpath[0]), tpath)) {
59226031Sstas	if (context)
60226031Sstas	    krb5_set_error_message(context, EINVAL,
61226031Sstas				   "Failed to get temporary path (GLE=%d)",
62226031Sstas				   GetLastError());
63226031Sstas	return EINVAL;
64226031Sstas    }
65226031Sstas
66226031Sstas    len = strlen(tpath);
67226031Sstas
68226031Sstas    if (len > 0 && tpath[len - 1] == '\\')
69226031Sstas	tpath[len - 1] = '\0';
70226031Sstas
71226031Sstas    *ret = strdup(tpath);
72226031Sstas
73226031Sstas    if (*ret == NULL) {
74226031Sstas	if (context)
75226031Sstas	    krb5_set_error_message(context, ENOMEM, "strdup - Out of memory");
76226031Sstas	return ENOMEM;
77226031Sstas    }
78226031Sstas
79226031Sstas    return 0;
80226031Sstas}
81226031Sstas
82226031Sstasextern HINSTANCE _krb5_hInstance;
83226031Sstas
84226031Sstas/*
85226031Sstas * Expand a %{BINDIR} token
86226031Sstas *
87226031Sstas * This is also used to expand a few other tokens on Windows, since
88226031Sstas * most of the executable binaries end up in the same directory.  The
89226031Sstas * "bin" directory is considered to be the directory in which the
90226031Sstas * krb5.dll is located.
91226031Sstas */
92226031Sstasstatic int
93226031Sstas_expand_bin_dir(krb5_context context, PTYPE param, const char *postfix, char **ret)
94226031Sstas{
95226031Sstas    TCHAR path[MAX_PATH];
96226031Sstas    TCHAR *lastSlash;
97226031Sstas    DWORD nc;
98226031Sstas
99226031Sstas    nc = GetModuleFileName(_krb5_hInstance, path, sizeof(path)/sizeof(path[0]));
100226031Sstas    if (nc == 0 ||
101226031Sstas	nc == sizeof(path)/sizeof(path[0])) {
102226031Sstas	return EINVAL;
103226031Sstas    }
104226031Sstas
105226031Sstas    lastSlash = strrchr(path, '\\');
106226031Sstas    if (lastSlash != NULL) {
107226031Sstas	TCHAR *fslash = strrchr(lastSlash, '/');
108226031Sstas
109226031Sstas	if (fslash != NULL)
110226031Sstas	    lastSlash = fslash;
111226031Sstas
112226031Sstas	*lastSlash = '\0';
113226031Sstas    }
114226031Sstas
115226031Sstas    if (postfix) {
116226031Sstas	if (strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0]))
117226031Sstas	    return EINVAL;
118226031Sstas    }
119226031Sstas
120226031Sstas    *ret = strdup(path);
121226031Sstas    if (*ret == NULL)
122226031Sstas	return ENOMEM;
123226031Sstas
124226031Sstas    return 0;
125226031Sstas}
126226031Sstas
127226031Sstas/*
128226031Sstas *  Expand a %{USERID} token
129226031Sstas *
130226031Sstas *  The %{USERID} token expands to the string representation of the
131226031Sstas *  user's SID.  The user account that will be used is the account
132226031Sstas *  corresponding to the current thread's security token.  This means
133226031Sstas *  that:
134226031Sstas *
135226031Sstas *  - If the current thread token has the anonymous impersonation
136226031Sstas *    level, the call will fail.
137226031Sstas *
138226031Sstas *  - If the current thread is impersonating a token at
139226031Sstas *    SecurityIdentification level the call will fail.
140226031Sstas *
141226031Sstas */
142226031Sstasstatic int
143226031Sstas_expand_userid(krb5_context context, PTYPE param, const char *postfix, char **ret)
144226031Sstas{
145226031Sstas    int rv = EINVAL;
146226031Sstas    HANDLE hThread = NULL;
147226031Sstas    HANDLE hToken = NULL;
148226031Sstas    PTOKEN_OWNER pOwner = NULL;
149226031Sstas    DWORD len = 0;
150226031Sstas    LPTSTR strSid = NULL;
151226031Sstas
152226031Sstas    hThread = GetCurrentThread();
153226031Sstas
154226031Sstas    if (!OpenThreadToken(hThread, TOKEN_QUERY,
155226031Sstas			 FALSE,	/* Open the thread token as the
156226031Sstas				   current thread user. */
157226031Sstas			 &hToken)) {
158226031Sstas
159226031Sstas	DWORD le = GetLastError();
160226031Sstas
161226031Sstas	if (le == ERROR_NO_TOKEN) {
162226031Sstas	    HANDLE hProcess = GetCurrentProcess();
163226031Sstas
164226031Sstas	    le = 0;
165226031Sstas	    if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
166226031Sstas		le = GetLastError();
167226031Sstas	}
168226031Sstas
169226031Sstas	if (le != 0) {
170226031Sstas	    if (context)
171226031Sstas		krb5_set_error_message(context, rv,
172226031Sstas				       "Can't open thread token (GLE=%d)", le);
173226031Sstas	    goto _exit;
174226031Sstas	}
175226031Sstas    }
176226031Sstas
177226031Sstas    if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) {
178226031Sstas	if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
179226031Sstas	    if (context)
180226031Sstas		krb5_set_error_message(context, rv,
181226031Sstas				       "Unexpected error reading token information (GLE=%d)",
182226031Sstas				       GetLastError());
183226031Sstas	    goto _exit;
184226031Sstas	}
185226031Sstas
186226031Sstas	if (len == 0) {
187226031Sstas	    if (context)
188226031Sstas		krb5_set_error_message(context, rv,
189226031Sstas				      "GetTokenInformation() returned truncated buffer");
190226031Sstas	    goto _exit;
191226031Sstas	}
192226031Sstas
193226031Sstas	pOwner = malloc(len);
194226031Sstas	if (pOwner == NULL) {
195226031Sstas	    if (context)
196226031Sstas		krb5_set_error_message(context, rv, "Out of memory");
197226031Sstas	    goto _exit;
198226031Sstas	}
199226031Sstas    } else {
200226031Sstas	if (context)
201226031Sstas	    krb5_set_error_message(context, rv, "GetTokenInformation() returned truncated buffer");
202226031Sstas	goto _exit;
203226031Sstas    }
204226031Sstas
205226031Sstas    if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
206226031Sstas	if (context)
207226031Sstas	    krb5_set_error_message(context, rv, "GetTokenInformation() failed. GLE=%d", GetLastError());
208226031Sstas	goto _exit;
209226031Sstas    }
210226031Sstas
211226031Sstas    if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
212226031Sstas	if (context)
213226031Sstas	    krb5_set_error_message(context, rv, "Can't convert SID to string. GLE=%d", GetLastError());
214226031Sstas	goto _exit;
215226031Sstas    }
216226031Sstas
217226031Sstas    *ret = strdup(strSid);
218226031Sstas    if (*ret == NULL && context)
219226031Sstas	krb5_set_error_message(context, rv, "Out of memory");
220226031Sstas
221226031Sstas    rv = 0;
222226031Sstas
223226031Sstas _exit:
224226031Sstas    if (hToken != NULL)
225226031Sstas	CloseHandle(hToken);
226226031Sstas
227226031Sstas    if (pOwner != NULL)
228226031Sstas	free (pOwner);
229226031Sstas
230226031Sstas    if (strSid != NULL)
231226031Sstas	LocalFree(strSid);
232226031Sstas
233226031Sstas    return rv;
234226031Sstas}
235226031Sstas
236226031Sstas/*
237226031Sstas * Expand a folder identified by a CSIDL
238226031Sstas */
239226031Sstas
240226031Sstasstatic int
241226031Sstas_expand_csidl(krb5_context context, PTYPE folder, const char *postfix, char **ret)
242226031Sstas{
243226031Sstas    TCHAR path[MAX_PATH];
244226031Sstas    size_t len;
245226031Sstas
246226031Sstas    if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path) != S_OK) {
247226031Sstas	if (context)
248226031Sstas	    krb5_set_error_message(context, EINVAL, "Unable to determine folder path");
249226031Sstas	return EINVAL;
250226031Sstas    }
251226031Sstas
252226031Sstas    len = strlen(path);
253226031Sstas
254226031Sstas    if (len > 0 && path[len - 1] == '\\')
255226031Sstas	path[len - 1] = '\0';
256226031Sstas
257226031Sstas    if (postfix &&
258226031Sstas	strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0])) {
259226031Sstas	return ENOMEM;
260226031Sstas    }
261226031Sstas
262226031Sstas    *ret = strdup(path);
263226031Sstas    if (*ret == NULL) {
264226031Sstas	if (context)
265226031Sstas	    krb5_set_error_message(context, ENOMEM, "Out of memory");
266226031Sstas	return ENOMEM;
267226031Sstas    }
268226031Sstas    return 0;
269226031Sstas}
270226031Sstas
271226031Sstas#else
272226031Sstas
273226031Sstasstatic int
274226031Sstas_expand_path(krb5_context context, PTYPE param, const char *postfix, char **ret)
275226031Sstas{
276226031Sstas    *ret = strdup(postfix);
277226031Sstas    if (*ret == NULL) {
278226031Sstas	krb5_set_error_message(context, ENOMEM, "malloc - out of memory");
279226031Sstas	return ENOMEM;
280226031Sstas    }
281226031Sstas    return 0;
282226031Sstas}
283226031Sstas
284226031Sstasstatic int
285226031Sstas_expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
286226031Sstas{
287226031Sstas    const char *p = NULL;
288226031Sstas
289226031Sstas    if (issuid())
290226031Sstas	p = getenv("TEMP");
291226031Sstas    if (p)
292226031Sstas	*ret = strdup(p);
293226031Sstas    else
294226031Sstas	*ret = strdup("/tmp");
295226031Sstas    if (*ret == NULL)
296226031Sstas	return ENOMEM;
297226031Sstas    return 0;
298226031Sstas}
299226031Sstas
300226031Sstasstatic int
301226031Sstas_expand_userid(krb5_context context, PTYPE param, const char *postfix, char **str)
302226031Sstas{
303226031Sstas    int ret = asprintf(str, "%ld", (unsigned long)getuid());
304226031Sstas    if (ret < 0 || *str == NULL)
305226031Sstas	return ENOMEM;
306226031Sstas    return 0;
307226031Sstas}
308226031Sstas
309226031Sstas
310226031Sstas#endif /* _WIN32 */
311226031Sstas
312226031Sstas/**
313226031Sstas * Expand a %{null} token
314226031Sstas *
315226031Sstas * The expansion of a %{null} token is always the empty string.
316226031Sstas */
317226031Sstas
318226031Sstasstatic int
319226031Sstas_expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret)
320226031Sstas{
321226031Sstas    *ret = strdup("");
322226031Sstas    if (*ret == NULL) {
323226031Sstas	if (context)
324226031Sstas	    krb5_set_error_message(context, ENOMEM, "Out of memory");
325226031Sstas	return ENOMEM;
326226031Sstas    }
327226031Sstas    return 0;
328226031Sstas}
329226031Sstas
330226031Sstas
331226031Sstasstatic const struct token {
332226031Sstas    const char * tok;
333226031Sstas    int ftype;
334226031Sstas#define FTYPE_CSIDL 0
335226031Sstas#define FTYPE_SPECIAL 1
336226031Sstas
337226031Sstas    PTYPE param;
338226031Sstas    const char * postfix;
339226031Sstas
340226031Sstas    int (*exp_func)(krb5_context, PTYPE, const char *, char **);
341226031Sstas
342226031Sstas#define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f
343226031Sstas#define SPECIAL(f) SPECIALP(f, NULL)
344226031Sstas
345226031Sstas} tokens[] = {
346226031Sstas#ifdef _WIN32
347226031Sstas#define CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl
348226031Sstas#define CSIDL(C) CSIDLP(C, NULL)
349226031Sstas
350226031Sstas    {"APPDATA", CSIDL(CSIDL_APPDATA)}, /* Roaming application data (for current user) */
351226031Sstas    {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)}, /* Application data (all users) */
352226031Sstas    {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)}, /* Local application data (for current user) */
353226031Sstas    {"SYSTEM", CSIDL(CSIDL_SYSTEM)}, /* Windows System folder (e.g. %WINDIR%\System32) */
354226031Sstas    {"WINDOWS", CSIDL(CSIDL_WINDOWS)}, /* Windows folder */
355226031Sstas    {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)}, /* Per user Heimdal configuration file path */
356226031Sstas    {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)}, /* Common Heimdal configuration file path */
357226031Sstas    {"LIBDIR", SPECIAL(_expand_bin_dir)},
358226031Sstas    {"BINDIR", SPECIAL(_expand_bin_dir)},
359226031Sstas    {"LIBEXEC", SPECIAL(_expand_bin_dir)},
360226031Sstas    {"SBINDIR", SPECIAL(_expand_bin_dir)},
361226031Sstas#else
362226031Sstas    {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, _expand_path},
363226031Sstas    {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, _expand_path},
364226031Sstas    {"LIBEXEC", FTYPE_SPECIAL, 0, LIBEXECDIR, _expand_path},
365226031Sstas    {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, _expand_path},
366226031Sstas#endif
367226031Sstas    {"TEMP", SPECIAL(_expand_temp_folder)},
368226031Sstas    {"USERID", SPECIAL(_expand_userid)},
369226031Sstas    {"uid", SPECIAL(_expand_userid)},
370226031Sstas    {"null", SPECIAL(_expand_null)}
371226031Sstas};
372226031Sstas
373226031Sstasstatic int
374226031Sstas_expand_token(krb5_context context,
375226031Sstas	      const char *token,
376226031Sstas	      const char *token_end,
377226031Sstas	      char **ret)
378226031Sstas{
379226031Sstas    size_t i;
380226031Sstas
381226031Sstas    *ret = NULL;
382226031Sstas
383226031Sstas    if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
384226031Sstas	token_end - token <= 2) {
385226031Sstas	if (context)
386226031Sstas	    krb5_set_error_message(context, EINVAL,"Invalid token.");
387226031Sstas	return EINVAL;
388226031Sstas    }
389226031Sstas
390226031Sstas    for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++) {
391226031Sstas	if (!strncmp(token+2, tokens[i].tok, (token_end - token) - 2))
392226031Sstas	    return tokens[i].exp_func(context, tokens[i].param,
393226031Sstas				      tokens[i].postfix, ret);
394226031Sstas    }
395226031Sstas
396226031Sstas    if (context)
397226031Sstas	krb5_set_error_message(context, EINVAL, "Invalid token.");
398226031Sstas    return EINVAL;
399226031Sstas}
400226031Sstas
401226031SstasKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
402226031Sstas_krb5_expand_path_tokens(krb5_context context,
403226031Sstas			 const char *path_in,
404226031Sstas			 char **ppath_out)
405226031Sstas{
406226031Sstas    char *tok_begin, *tok_end, *append;
407226031Sstas    const char *path_left;
408226031Sstas    size_t len = 0;
409226031Sstas
410226031Sstas    if (path_in == NULL || *path_in == '\0') {
411226031Sstas        *ppath_out = strdup("");
412226031Sstas        return 0;
413226031Sstas    }
414226031Sstas
415226031Sstas    *ppath_out = NULL;
416226031Sstas
417226031Sstas    for (path_left = path_in; path_left && *path_left; ) {
418226031Sstas
419226031Sstas	tok_begin = strstr(path_left, "%{");
420226031Sstas
421226031Sstas	if (tok_begin && tok_begin != path_left) {
422226031Sstas
423226031Sstas	    append = malloc((tok_begin - path_left) + 1);
424226031Sstas	    if (append) {
425226031Sstas		memcpy(append, path_left, tok_begin - path_left);
426226031Sstas		append[tok_begin - path_left] = '\0';
427226031Sstas	    }
428226031Sstas	    path_left = tok_begin;
429226031Sstas
430226031Sstas	} else if (tok_begin) {
431226031Sstas
432226031Sstas	    tok_end = strchr(tok_begin, '}');
433226031Sstas	    if (tok_end == NULL) {
434226031Sstas		if (*ppath_out)
435226031Sstas		    free(*ppath_out);
436226031Sstas		*ppath_out = NULL;
437226031Sstas		if (context)
438226031Sstas		    krb5_set_error_message(context, EINVAL, "variable missing }");
439226031Sstas		return EINVAL;
440226031Sstas	    }
441226031Sstas
442226031Sstas	    if (_expand_token(context, tok_begin, tok_end, &append)) {
443226031Sstas		if (*ppath_out)
444226031Sstas		    free(*ppath_out);
445226031Sstas		*ppath_out = NULL;
446226031Sstas		return EINVAL;
447226031Sstas	    }
448226031Sstas
449226031Sstas	    path_left = tok_end + 1;
450226031Sstas	} else {
451226031Sstas
452226031Sstas	    append = strdup(path_left);
453226031Sstas	    path_left = NULL;
454226031Sstas
455226031Sstas	}
456226031Sstas
457226031Sstas	if (append == NULL) {
458226031Sstas
459226031Sstas	    if (*ppath_out)
460226031Sstas		free(*ppath_out);
461226031Sstas	    *ppath_out = NULL;
462226031Sstas	    if (context)
463226031Sstas		krb5_set_error_message(context, ENOMEM, "malloc - out of memory");
464226031Sstas	    return ENOMEM;
465226031Sstas
466226031Sstas	}
467226031Sstas
468226031Sstas	{
469226031Sstas	    size_t append_len = strlen(append);
470226031Sstas	    char * new_str = realloc(*ppath_out, len + append_len + 1);
471226031Sstas
472226031Sstas	    if (new_str == NULL) {
473226031Sstas		free(append);
474226031Sstas		if (*ppath_out)
475226031Sstas		    free(*ppath_out);
476226031Sstas		*ppath_out = NULL;
477226031Sstas		if (context)
478226031Sstas		    krb5_set_error_message(context, ENOMEM, "malloc - out of memory");
479226031Sstas		return ENOMEM;
480226031Sstas	    }
481226031Sstas
482226031Sstas	    *ppath_out = new_str;
483226031Sstas	    memcpy(*ppath_out + len, append, append_len + 1);
484226031Sstas	    len = len + append_len;
485226031Sstas	    free(append);
486226031Sstas	}
487226031Sstas    }
488226031Sstas
489226031Sstas#ifdef _WIN32
490226031Sstas    /* Also deal with slashes */
491226031Sstas    if (*ppath_out) {
492226031Sstas	char * c;
493226031Sstas	for (c = *ppath_out; *c; c++)
494226031Sstas	    if (*c == '/')
495226031Sstas		*c = '\\';
496226031Sstas    }
497226031Sstas#endif
498226031Sstas
499226031Sstas    return 0;
500226031Sstas}
501