1 /*	$NetBSD: expand_path.c,v 1.2 2017/01/28 21:31:49 christos 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 #include <stdarg.h>
38 
39 typedef int PTYPE;
40 
41 #ifdef _WIN32
42 #include <shlobj.h>
43 #include <sddl.h>
44 
45 /*
46  * Expand a %{TEMP} token
47  *
48  * The %{TEMP} token expands to the temporary path for the current
49  * user as returned by GetTempPath().
50  *
51  * @note: Since the GetTempPath() function relies on the TMP or TEMP
52  * environment variables, this function will failover to the system
53  * temporary directory until the user profile is loaded.  In addition,
54  * the returned path may or may not exist.
55  */
56 static krb5_error_code
_expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)57 _expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
58 {
59     TCHAR tpath[MAX_PATH];
60     size_t len;
61 
62     if (!GetTempPath(sizeof(tpath)/sizeof(tpath[0]), tpath)) {
63 	if (context)
64 	    krb5_set_error_message(context, EINVAL,
65 				   "Failed to get temporary path (GLE=%d)",
66 				   GetLastError());
67 	return EINVAL;
68     }
69 
70     len = strlen(tpath);
71 
72     if (len > 0 && tpath[len - 1] == '\\')
73 	tpath[len - 1] = '\0';
74 
75     *ret = strdup(tpath);
76 
77     if (*ret == NULL)
78 	return krb5_enomem(context);
79 
80     return 0;
81 }
82 
83 extern HINSTANCE _krb5_hInstance;
84 
85 /*
86  * Expand a %{BINDIR} token
87  *
88  * This is also used to expand a few other tokens on Windows, since
89  * most of the executable binaries end up in the same directory.  The
90  * "bin" directory is considered to be the directory in which the
91  * krb5.dll is located.
92  */
93 static krb5_error_code
_expand_bin_dir(krb5_context context,PTYPE param,const char * postfix,char ** ret)94 _expand_bin_dir(krb5_context context, PTYPE param, const char *postfix, char **ret)
95 {
96     TCHAR path[MAX_PATH];
97     TCHAR *lastSlash;
98     DWORD nc;
99 
100     nc = GetModuleFileName(_krb5_hInstance, path, sizeof(path)/sizeof(path[0]));
101     if (nc == 0 ||
102 	nc == sizeof(path)/sizeof(path[0])) {
103 	return EINVAL;
104     }
105 
106     lastSlash = strrchr(path, '\\');
107     if (lastSlash != NULL) {
108 	TCHAR *fslash = strrchr(lastSlash, '/');
109 
110 	if (fslash != NULL)
111 	    lastSlash = fslash;
112 
113 	*lastSlash = '\0';
114     }
115 
116     if (postfix) {
117 	if (strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0]))
118 	    return EINVAL;
119     }
120 
121     *ret = strdup(path);
122     if (*ret == NULL)
123 	return krb5_enomem(context);
124 
125     return 0;
126 }
127 
128 /*
129  *  Expand a %{USERID} token
130  *
131  *  The %{USERID} token expands to the string representation of the
132  *  user's SID.  The user account that will be used is the account
133  *  corresponding to the current thread's security token.  This means
134  *  that:
135  *
136  *  - If the current thread token has the anonymous impersonation
137  *    level, the call will fail.
138  *
139  *  - If the current thread is impersonating a token at
140  *    SecurityIdentification level the call will fail.
141  *
142  */
143 static krb5_error_code
_expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** ret)144 _expand_userid(krb5_context context, PTYPE param, const char *postfix, char **ret)
145 {
146     int rv = EINVAL;
147     HANDLE hThread = NULL;
148     HANDLE hToken = NULL;
149     PTOKEN_OWNER pOwner = NULL;
150     DWORD len = 0;
151     LPTSTR strSid = NULL;
152 
153     hThread = GetCurrentThread();
154 
155     if (!OpenThreadToken(hThread, TOKEN_QUERY,
156 			 FALSE,	/* Open the thread token as the
157 				   current thread user. */
158 			 &hToken)) {
159 
160 	DWORD le = GetLastError();
161 
162 	if (le == ERROR_NO_TOKEN) {
163 	    HANDLE hProcess = GetCurrentProcess();
164 
165 	    le = 0;
166 	    if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
167 		le = GetLastError();
168 	}
169 
170 	if (le != 0) {
171 	    if (context)
172 		krb5_set_error_message(context, rv,
173 				       "Can't open thread token (GLE=%d)", le);
174 	    goto _exit;
175 	}
176     }
177 
178     if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) {
179 	if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
180 	    if (context)
181 		krb5_set_error_message(context, rv,
182 				       "Unexpected error reading token information (GLE=%d)",
183 				       GetLastError());
184 	    goto _exit;
185 	}
186 
187 	if (len == 0) {
188 	    if (context)
189 		krb5_set_error_message(context, rv,
190 				      "GetTokenInformation() returned truncated buffer");
191 	    goto _exit;
192 	}
193 
194 	pOwner = malloc(len);
195 	if (pOwner == NULL) {
196 	    if (context)
197 		krb5_set_error_message(context, rv, "Out of memory");
198 	    goto _exit;
199 	}
200     } else {
201 	if (context)
202 	    krb5_set_error_message(context, rv, "GetTokenInformation() returned truncated buffer");
203 	goto _exit;
204     }
205 
206     if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
207 	if (context)
208 	    krb5_set_error_message(context, rv, "GetTokenInformation() failed. GLE=%d", GetLastError());
209 	goto _exit;
210     }
211 
212     if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
213 	if (context)
214 	    krb5_set_error_message(context, rv, "Can't convert SID to string. GLE=%d", GetLastError());
215 	goto _exit;
216     }
217 
218     *ret = strdup(strSid);
219     if (*ret == NULL && context)
220 	krb5_set_error_message(context, rv, "Out of memory");
221 
222     rv = 0;
223 
224  _exit:
225     if (hToken != NULL)
226 	CloseHandle(hToken);
227 
228     if (pOwner != NULL)
229 	free (pOwner);
230 
231     if (strSid != NULL)
232 	LocalFree(strSid);
233 
234     return rv;
235 }
236 
237 /*
238  * Expand a folder identified by a CSIDL
239  */
240 
241 static krb5_error_code
_expand_csidl(krb5_context context,PTYPE folder,const char * postfix,char ** ret)242 _expand_csidl(krb5_context context, PTYPE folder, const char *postfix, char **ret)
243 {
244     TCHAR path[MAX_PATH];
245     size_t len;
246 
247     if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT, path) != S_OK) {
248 	if (context)
249 	    krb5_set_error_message(context, EINVAL, "Unable to determine folder path");
250 	return EINVAL;
251     }
252 
253     len = strlen(path);
254 
255     if (len > 0 && path[len - 1] == '\\')
256 	path[len - 1] = '\0';
257 
258     if (postfix &&
259 	strlcat(path, postfix, sizeof(path)/sizeof(path[0])) >= sizeof(path)/sizeof(path[0]))
260 	return krb5_enomem(context);
261 
262     *ret = strdup(path);
263     if (*ret == NULL)
264 	return krb5_enomem(context);
265     return 0;
266 }
267 
268 #else
269 
270 static krb5_error_code
_expand_path(krb5_context context,PTYPE param,const char * postfix,char ** ret)271 _expand_path(krb5_context context, PTYPE param, const char *postfix, char **ret)
272 {
273     *ret = strdup(postfix);
274     if (*ret == NULL)
275 	return krb5_enomem(context);
276     return 0;
277 }
278 
279 static krb5_error_code
_expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)280 _expand_temp_folder(krb5_context context, PTYPE param, const char *postfix, char **ret)
281 {
282     const char *p = NULL;
283 
284     if (!issuid())
285 	p = getenv("TEMP");
286 
287     if (p)
288 	*ret = strdup(p);
289     else
290 	*ret = strdup("/tmp");
291     if (*ret == NULL)
292 	return krb5_enomem(context);
293     return 0;
294 }
295 
296 static krb5_error_code
_expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** str)297 _expand_userid(krb5_context context, PTYPE param, const char *postfix, char **str)
298 {
299     int ret = asprintf(str, "%ld", (unsigned long)getuid());
300     if (ret < 0 || *str == NULL)
301 	return krb5_enomem(context);
302     return 0;
303 }
304 
305 
306 #endif /* _WIN32 */
307 
308 /**
309  * Expand an extra token
310  */
311 
312 static krb5_error_code
_expand_extra_token(krb5_context context,const char * value,char ** ret)313 _expand_extra_token(krb5_context context, const char *value, char **ret)
314 {
315     *ret = strdup(value);
316     if (*ret == NULL)
317 	return krb5_enomem(context);
318     return 0;
319 }
320 
321 /**
322  * Expand a %{null} token
323  *
324  * The expansion of a %{null} token is always the empty string.
325  */
326 
327 static krb5_error_code
_expand_null(krb5_context context,PTYPE param,const char * postfix,char ** ret)328 _expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret)
329 {
330     *ret = strdup("");
331     if (*ret == NULL)
332 	return krb5_enomem(context);
333     return 0;
334 }
335 
336 
337 static const struct {
338     const char * tok;
339     int ftype;
340 #define FTYPE_CSIDL 0
341 #define FTYPE_SPECIAL 1
342 
343     PTYPE param;
344     const char * postfix;
345 
346     int (*exp_func)(krb5_context, PTYPE, const char *, char **);
347 
348 #define SPECIALP(f, P) FTYPE_SPECIAL, 0, P, f
349 #define SPECIAL(f) SPECIALP(f, NULL)
350 
351 } tokens[] = {
352 #ifdef _WIN32
353 #define CSIDLP(C,P) FTYPE_CSIDL, C, P, _expand_csidl
354 #define CSIDL(C) CSIDLP(C, NULL)
355 
356     {"APPDATA", CSIDL(CSIDL_APPDATA)}, /* Roaming application data (for current user) */
357     {"COMMON_APPDATA", CSIDL(CSIDL_COMMON_APPDATA)}, /* Application data (all users) */
358     {"LOCAL_APPDATA", CSIDL(CSIDL_LOCAL_APPDATA)}, /* Local application data (for current user) */
359     {"SYSTEM", CSIDL(CSIDL_SYSTEM)}, /* Windows System folder (e.g. %WINDIR%\System32) */
360     {"WINDOWS", CSIDL(CSIDL_WINDOWS)}, /* Windows folder */
361     {"USERCONFIG", CSIDLP(CSIDL_APPDATA, "\\" PACKAGE)}, /* Per user Heimdal configuration file path */
362     {"COMMONCONFIG", CSIDLP(CSIDL_COMMON_APPDATA, "\\" PACKAGE)}, /* Common Heimdal configuration file path */
363     {"LIBDIR", SPECIAL(_expand_bin_dir)},
364     {"BINDIR", SPECIAL(_expand_bin_dir)},
365     {"LIBEXEC", SPECIAL(_expand_bin_dir)},
366     {"SBINDIR", SPECIAL(_expand_bin_dir)},
367 #else
368     {"LIBDIR", FTYPE_SPECIAL, 0, LIBDIR, _expand_path},
369     {"BINDIR", FTYPE_SPECIAL, 0, BINDIR, _expand_path},
370     {"LIBEXEC", FTYPE_SPECIAL, 0, LIBEXECDIR, _expand_path},
371     {"SBINDIR", FTYPE_SPECIAL, 0, SBINDIR, _expand_path},
372 #endif
373     {"TEMP", SPECIAL(_expand_temp_folder)},
374     {"USERID", SPECIAL(_expand_userid)},
375     {"uid", SPECIAL(_expand_userid)},
376     {"null", SPECIAL(_expand_null)}
377 };
378 
379 static krb5_error_code
_expand_token(krb5_context context,const char * token,const char * token_end,char ** extra_tokens,char ** ret)380 _expand_token(krb5_context context,
381 	      const char *token,
382 	      const char *token_end,
383 	      char **extra_tokens,
384 	      char **ret)
385 {
386     size_t i;
387     char **p;
388 
389     *ret = NULL;
390 
391     if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
392 	token_end - token <= 2) {
393 	if (context)
394 	    krb5_set_error_message(context, EINVAL,"Invalid token.");
395 	return EINVAL;
396     }
397 
398     for (p = extra_tokens; p && p[0]; p += 2) {
399 	if (strncmp(token+2, p[0], (token_end - token) - 2) == 0)
400 	    return _expand_extra_token(context, p[1], ret);
401     }
402 
403     for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++) {
404 	if (!strncmp(token+2, tokens[i].tok, (token_end - token) - 2))
405 	    return tokens[i].exp_func(context, tokens[i].param,
406 				      tokens[i].postfix, ret);
407     }
408 
409     if (context)
410 	krb5_set_error_message(context, EINVAL, "Invalid token.");
411     return EINVAL;
412 }
413 
414 /**
415  * Internal function to expand tokens in paths.
416  *
417  * Inputs:
418  *
419  * @context   A krb5_context
420  * @path_in   The path to expand tokens from
421  *
422  * Outputs:
423  *
424  * @ppath_out Path with expanded tokens (caller must free() this)
425  */
426 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_expand_path_tokens(krb5_context context,const char * path_in,int filepath,char ** ppath_out)427 _krb5_expand_path_tokens(krb5_context context,
428 			 const char *path_in,
429 			 int filepath,
430 			 char **ppath_out)
431 {
432     return _krb5_expand_path_tokensv(context, path_in, filepath, ppath_out, NULL);
433 }
434 
435 static void
free_extra_tokens(char ** extra_tokens)436 free_extra_tokens(char **extra_tokens)
437 {
438     char **p;
439 
440     for (p = extra_tokens; p && *p; p++)
441 	free(*p);
442     free(extra_tokens);
443 }
444 
445 /**
446  * Internal function to expand tokens in paths.
447  *
448  * Inputs:
449  *
450  * @context   A krb5_context
451  * @path_in   The path to expand tokens from
452  * @ppath_out The expanded path
453  * @...       Variable number of pairs of strings, the first of each
454  *            being a token (e.g., "luser") and the second a string to
455  *            replace it with.  The list is terminated by a NULL.
456  *
457  * Outputs:
458  *
459  * @ppath_out Path with expanded tokens (caller must free() this)
460  */
461 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_expand_path_tokensv(krb5_context context,const char * path_in,int filepath,char ** ppath_out,...)462 _krb5_expand_path_tokensv(krb5_context context,
463 			  const char *path_in,
464 			  int filepath,
465 			  char **ppath_out, ...)
466 {
467     char *tok_begin, *tok_end, *append;
468     char **extra_tokens = NULL;
469     const char *path_left;
470     size_t nargs = 0;
471     size_t len = 0;
472     va_list ap;
473 
474     if (path_in == NULL || *path_in == '\0') {
475         *ppath_out = strdup("");
476         return 0;
477     }
478 
479     *ppath_out = NULL;
480 
481     va_start(ap, ppath_out);
482     while (va_arg(ap, const char *)) {
483 	nargs++;
484         va_arg(ap, const char *);
485     }
486     va_end(ap);
487     nargs *= 2;
488 
489     /* Get extra tokens */
490     if (nargs) {
491 	size_t i;
492 
493 	extra_tokens = calloc(nargs + 1, sizeof (*extra_tokens));
494 	if (extra_tokens == NULL)
495 	    return krb5_enomem(context);
496 	va_start(ap, ppath_out);
497 	for (i = 0; i < nargs; i++) {
498 	    const char *s = va_arg(ap, const char *); /* token key */
499 	    if (s == NULL)
500 		break;
501 	    extra_tokens[i] = strdup(s);
502 	    if (extra_tokens[i++] == NULL) {
503 		va_end(ap);
504 		free_extra_tokens(extra_tokens);
505 		return krb5_enomem(context);
506 	    }
507 	    s = va_arg(ap, const char *); /* token value */
508 	    if (s == NULL)
509 		s = "";
510 	    extra_tokens[i] = strdup(s);
511 	    if (extra_tokens[i] == NULL) {
512 		va_end(ap);
513 		free_extra_tokens(extra_tokens);
514 		return krb5_enomem(context);
515 	    }
516 	}
517 	va_end(ap);
518     }
519 
520     for (path_left = path_in; path_left && *path_left; ) {
521 
522 	tok_begin = strstr(path_left, "%{");
523 
524 	if (tok_begin && tok_begin != path_left) {
525 
526 	    append = malloc((tok_begin - path_left) + 1);
527 	    if (append) {
528 		memcpy(append, path_left, tok_begin - path_left);
529 		append[tok_begin - path_left] = '\0';
530 	    }
531 	    path_left = tok_begin;
532 
533 	} else if (tok_begin) {
534 
535 	    tok_end = strchr(tok_begin, '}');
536 	    if (tok_end == NULL) {
537 		free_extra_tokens(extra_tokens);
538 		if (*ppath_out)
539 		    free(*ppath_out);
540 		*ppath_out = NULL;
541 		if (context)
542 		    krb5_set_error_message(context, EINVAL, "variable missing }");
543 		return EINVAL;
544 	    }
545 
546 	    if (_expand_token(context, tok_begin, tok_end, extra_tokens,
547 			      &append)) {
548 		free_extra_tokens(extra_tokens);
549 		if (*ppath_out)
550 		    free(*ppath_out);
551 		*ppath_out = NULL;
552 		return EINVAL;
553 	    }
554 
555 	    path_left = tok_end + 1;
556 	} else {
557 
558 	    append = strdup(path_left);
559 	    path_left = NULL;
560 
561 	}
562 
563 	if (append == NULL) {
564 
565 	    free_extra_tokens(extra_tokens);
566 	    if (*ppath_out)
567 		free(*ppath_out);
568 	    *ppath_out = NULL;
569 	    return krb5_enomem(context);
570 
571 	}
572 
573 	{
574 	    size_t append_len = strlen(append);
575 	    char * new_str = realloc(*ppath_out, len + append_len + 1);
576 
577 	    if (new_str == NULL) {
578 		free_extra_tokens(extra_tokens);
579 		free(append);
580 		if (*ppath_out)
581 		    free(*ppath_out);
582 		*ppath_out = NULL;
583 		return krb5_enomem(context);
584 	    }
585 
586 	    *ppath_out = new_str;
587 	    memcpy(*ppath_out + len, append, append_len + 1);
588 	    len = len + append_len;
589 	    free(append);
590 	}
591     }
592 
593 #ifdef _WIN32
594     /* Also deal with slashes */
595     if (filepath && *ppath_out) {
596 	char * c;
597 
598 	for (c = *ppath_out; *c; c++)
599 	    if (*c == '/')
600 		*c = '\\';
601     }
602 #endif
603 
604     free_extra_tokens(extra_tokens);
605     return 0;
606 }
607