expand_path.c revision 1.1.1.1.10.1
1/*	$NetBSD: expand_path.c,v 1.1.1.1.10.1 2014/08/19 23:45:19 tls 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
37typedef 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 */
54static 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
84extern 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 */
94static 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 */
144static 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
242static 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
275static 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
286static 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
302static 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
320static 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
333static 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
375static 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
403KRB5_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