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