1 /*	$NetBSD: expand_path.c,v 1.1.1.2 2014/04/24 12:45:50 pettai 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 
37 typedef 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  */
54 static int
_expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)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 
84 extern 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  */
94 static int
_expand_bin_dir(krb5_context context,PTYPE param,const char * postfix,char ** ret)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  */
144 static int
_expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** ret)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 
242 static int
_expand_csidl(krb5_context context,PTYPE folder,const char * postfix,char ** ret)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 
275 static int
_expand_path(krb5_context context,PTYPE param,const char * postfix,char ** ret)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 
286 static int
_expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)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 
302 static int
_expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** str)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 
320 static int
_expand_null(krb5_context context,PTYPE param,const char * postfix,char ** ret)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 
333 static 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 
375 static int
_expand_token(krb5_context context,const char * token,const char * token_end,char ** ret)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 
403 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_expand_path_tokens(krb5_context context,const char * path_in,char ** ppath_out)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