xref: /freebsd/crypto/heimdal/lib/krb5/expand_path.c (revision dad64f0e)
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 
35 typedef 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  */
52 static 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 
82 extern 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  */
92 static 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  */
142 static 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 
240 static 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 
273 static 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 
284 static 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 
300 static 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 
318 static 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 
331 static 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 
373 static 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 
401 KRB5_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