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