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
39typedef 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 */
56static krb5_error_code
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
83extern 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 */
93static krb5_error_code
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 */
143static krb5_error_code
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
241static krb5_error_code
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
270static krb5_error_code
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
279static krb5_error_code
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
296static krb5_error_code
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
312static krb5_error_code
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
327static krb5_error_code
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
337static 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
379static krb5_error_code
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 */
426KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
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
435static void
436free_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 */
461KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
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