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
_expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)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
_expand_bin_dir(krb5_context context,PTYPE param,const char * postfix,char ** ret)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
_expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** ret)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
_expand_csidl(krb5_context context,PTYPE folder,const char * postfix,char ** ret)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
_expand_path(krb5_context context,PTYPE param,const char * postfix,char ** ret)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
_expand_temp_folder(krb5_context context,PTYPE param,const char * postfix,char ** ret)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
_expand_userid(krb5_context context,PTYPE param,const char * postfix,char ** str)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
_expand_null(krb5_context context,PTYPE param,const char * postfix,char ** ret)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
_expand_token(krb5_context context,const char * token,const char * token_end,char ** ret)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
_krb5_expand_path_tokens(krb5_context context,const char * path_in,char ** ppath_out)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