Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / lib / krb5 / os / expand_path.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/expand_path.c - Parameterized path expansion facility */
3 /*
4  * Copyright (c) 2009, Secure Endpoints Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * - Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * - Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "k5-int.h"
34 #include "os-proto.h"
35
36 typedef int PTYPE;
37
38 #ifdef _WIN32
39 #include <shlobj.h>
40 #include <sddl.h>
41
42 /*
43  * Expand a %{TEMP} token
44  *
45  * The %{TEMP} token expands to the temporary path for the current
46  * user as returned by GetTempPath().
47  *
48  * @note: Since the GetTempPath() function relies on the TMP or TEMP
49  * environment variables, this function will failover to the system
50  * temporary directory until the user profile is loaded.  In addition,
51  * the returned path may or may not exist.
52  */
53 static krb5_error_code
54 expand_temp_folder(krb5_context context, PTYPE param, const char *postfix,
55                    char **ret)
56 {
57     TCHAR tpath[MAX_PATH];
58     size_t len;
59
60     if (!GetTempPath(sizeof(tpath) / sizeof(tpath[0]), tpath)) {
61         k5_setmsg(context, EINVAL, "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         return ENOMEM;
75
76     return 0;
77 }
78
79 /*
80  * Expand a %{BINDIR} token
81  *
82  * This is also used to expand a few other tokens on Windows, since
83  * most of the executable binaries end up in the same directory.  The
84  * "bin" directory is considered to be the directory in which the
85  * krb5.dll is located.
86  */
87 static krb5_error_code
88 expand_bin_dir(krb5_context context, PTYPE param, const char *postfix,
89                char **ret)
90 {
91     TCHAR path[MAX_PATH];
92     TCHAR *lastSlash;
93     DWORD nc;
94
95     nc = GetModuleFileName(get_lib_instance(), path,
96                            sizeof(path) / sizeof(path[0]));
97     if (nc == 0 ||
98         nc == sizeof(path) / sizeof(path[0])) {
99         return EINVAL;
100     }
101
102     lastSlash = strrchr(path, '\\');
103     if (lastSlash != NULL) {
104         TCHAR *fslash = strrchr(lastSlash, '/');
105
106         if (fslash != NULL)
107             lastSlash = fslash;
108
109         *lastSlash = '\0';
110     }
111
112     if (postfix) {
113         if (strlcat(path, postfix, sizeof(path) / sizeof(path[0])) >=
114             sizeof(path) / sizeof(path[0]))
115             return EINVAL;
116     }
117
118     *ret = strdup(path);
119     if (*ret == NULL)
120         return ENOMEM;
121
122     return 0;
123 }
124
125 /*
126  *  Expand a %{USERID} token
127  *
128  *  The %{USERID} token expands to the string representation of the
129  *  user's SID.  The user account that will be used is the account
130  *  corresponding to the current thread's security token.  This means
131  *  that:
132  *
133  *  - If the current thread token has the anonymous impersonation
134  *    level, the call will fail.
135  *
136  *  - If the current thread is impersonating a token at
137  *    SecurityIdentification level the call will fail.
138  *
139  */
140 static krb5_error_code
141 expand_userid(krb5_context context, PTYPE param, const char *postfix,
142               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             k5_setmsg(context, rv, "Can't open thread token (GLE=%d)", le);
170             goto cleanup;
171         }
172     }
173
174     if (!GetTokenInformation(hToken, TokenOwner, NULL, 0, &len)) {
175         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
176             k5_setmsg(context, rv,
177                       "Unexpected error reading token information (GLE=%d)",
178                       GetLastError());
179             goto cleanup;
180         }
181
182         if (len == 0) {
183             k5_setmsg(context, rv,
184                       "GetTokenInformation() returned truncated buffer");
185             goto cleanup;
186         }
187
188         pOwner = malloc(len);
189         if (pOwner == NULL) {
190             rv = ENOMEM;
191             goto cleanup;
192         }
193     } else {
194         k5_setmsg(context, rv,
195                   "GetTokenInformation() returned truncated buffer");
196         goto cleanup;
197     }
198
199     if (!GetTokenInformation(hToken, TokenOwner, pOwner, len, &len)) {
200         k5_setmsg(context, rv,
201                   "GetTokenInformation() failed.  GLE=%d", GetLastError());
202         goto cleanup;
203     }
204
205     if (!ConvertSidToStringSid(pOwner->Owner, &strSid)) {
206         k5_setmsg(context, rv,
207                   "Can't convert SID to string.  GLE=%d", GetLastError());
208         goto cleanup;
209     }
210
211     *ret = strdup(strSid);
212     if (*ret == NULL) {
213         rv = ENOMEM;
214         goto cleanup;
215     }
216
217     rv = 0;
218
219 cleanup:
220     if (hToken != NULL)
221         CloseHandle(hToken);
222
223     if (pOwner != NULL)
224         free(pOwner);
225
226     if (strSid != NULL)
227         LocalFree(strSid);
228
229     return rv;
230 }
231
232 /*
233  * Expand a folder identified by a CSIDL
234  */
235 static krb5_error_code
236 expand_csidl(krb5_context context, PTYPE folder, const char *postfix,
237              char **ret)
238 {
239     TCHAR path[MAX_PATH];
240     size_t len;
241
242     if (SHGetFolderPath(NULL, folder, NULL, SHGFP_TYPE_CURRENT,
243                         path) != S_OK) {
244         k5_setmsg(context, EINVAL, "Unable to determine folder path");
245         return EINVAL;
246     }
247
248     len = strlen(path);
249
250     if (len > 0 && path[len - 1] == '\\')
251         path[len - 1] = '\0';
252
253     if (postfix &&
254         strlcat(path, postfix, sizeof(path) / sizeof(path[0])) >=
255         sizeof(path)/sizeof(path[0]))
256         return ENOMEM;
257
258     *ret = strdup(path);
259     if (*ret == NULL)
260         return ENOMEM;
261     return 0;
262 }
263
264 #else /* not _WIN32 */
265 #include <pwd.h>
266
267 static krb5_error_code
268 expand_path(krb5_context context, PTYPE param, const char *postfix, char **ret)
269 {
270     *ret = strdup(postfix);
271     if (*ret == NULL)
272         return ENOMEM;
273     return 0;
274 }
275
276 static krb5_error_code
277 expand_temp_folder(krb5_context context, PTYPE param, const char *postfix,
278                    char **ret)
279 {
280     const char *p = NULL;
281
282     if (context == NULL || !context->profile_secure)
283         p = getenv("TMPDIR");
284     *ret = strdup((p != NULL) ? p : "/tmp");
285     if (*ret == NULL)
286         return ENOMEM;
287     return 0;
288 }
289
290 static krb5_error_code
291 expand_userid(krb5_context context, PTYPE param, const char *postfix,
292               char **str)
293 {
294     if (asprintf(str, "%lu", (unsigned long)getuid()) < 0)
295         return ENOMEM;
296     return 0;
297 }
298
299 static krb5_error_code
300 expand_euid(krb5_context context, PTYPE param, const char *postfix, char **str)
301 {
302     if (asprintf(str, "%lu", (unsigned long)geteuid()) < 0)
303         return ENOMEM;
304     return 0;
305 }
306
307 static krb5_error_code
308 expand_username(krb5_context context, PTYPE param, const char *postfix,
309                 char **str)
310 {
311     uid_t euid = geteuid();
312     struct passwd *pw, pwx;
313     char pwbuf[BUFSIZ];
314
315     if (k5_getpwuid_r(euid, &pwx, pwbuf, sizeof(pwbuf), &pw) != 0) {
316         k5_setmsg(context, ENOENT, _("Can't find username for uid %lu"),
317                   (unsigned long)euid);
318         return ENOENT;
319     }
320     *str = strdup(pw->pw_name);
321     if (*str == NULL)
322         return ENOMEM;
323     return 0;
324 }
325
326 #endif /* not _WIN32 */
327
328 /*
329  * Expand an extra token
330  */
331 static krb5_error_code
332 expand_extra_token(krb5_context context, const char *value, char **ret)
333 {
334     *ret = strdup(value);
335     if (*ret == NULL)
336         return ENOMEM;
337     return 0;
338 }
339
340 /*
341  * Expand a %{null} token
342  *
343  * The expansion of a %{null} token is always the empty string.
344  */
345 static krb5_error_code
346 expand_null(krb5_context context, PTYPE param, const char *postfix, char **ret)
347 {
348     *ret = strdup("");
349     if (*ret == NULL)
350         return ENOMEM;
351     return 0;
352 }
353
354 static const struct token {
355     const char *tok;
356     PTYPE param;
357     const char *postfix;
358     int (*exp_func)(krb5_context, PTYPE, const char *, char **);
359 } tokens[] = {
360 #ifdef _WIN32
361     /* Roaming application data (for current user) */
362     {"APPDATA", CSIDL_APPDATA, NULL, expand_csidl},
363     /* Application data (all users) */
364     {"COMMON_APPDATA", CSIDL_COMMON_APPDATA, NULL, expand_csidl},
365     /* Local application data (for current user) */
366     {"LOCAL_APPDATA", CSIDL_LOCAL_APPDATA, NULL, expand_csidl},
367     /* Windows System folder (e.g. %WINDIR%\System32) */
368     {"SYSTEM", CSIDL_SYSTEM, NULL, expand_csidl},
369     /* Windows folder */
370     {"WINDOWS", CSIDL_WINDOWS, NULL, expand_csidl},
371     /* Per user MIT krb5 configuration file directory */
372     {"USERCONFIG", CSIDL_APPDATA, "\\MIT\\Kerberos5",
373      expand_csidl},
374     /* Common MIT krb5 configuration file directory */
375     {"COMMONCONFIG", CSIDL_COMMON_APPDATA, "\\MIT\\Kerberos5",
376      expand_csidl},
377     {"LIBDIR", 0, NULL, expand_bin_dir},
378     {"BINDIR", 0, NULL, expand_bin_dir},
379     {"SBINDIR", 0, NULL, expand_bin_dir},
380     {"euid", 0, NULL, expand_userid},
381 #else
382     {"LIBDIR", 0, LIBDIR, expand_path},
383     {"BINDIR", 0, BINDIR, expand_path},
384     {"SBINDIR", 0, SBINDIR, expand_path},
385     {"euid", 0, NULL, expand_euid},
386     {"username", 0, NULL, expand_username},
387 #endif
388     {"TEMP", 0, NULL, expand_temp_folder},
389     {"USERID", 0, NULL, expand_userid},
390     {"uid", 0, NULL, expand_userid},
391     {"null", 0, NULL, expand_null}
392 };
393
394 static krb5_error_code
395 expand_token(krb5_context context, const char *token, const char *token_end,
396              char **extra_tokens, char **ret)
397 {
398     size_t i;
399     char **p;
400
401     *ret = NULL;
402
403     if (token[0] != '%' || token[1] != '{' || token_end[0] != '}' ||
404         token_end - token <= 2) {
405         k5_setmsg(context, EINVAL, _("Invalid token"));
406         return EINVAL;
407     }
408
409     for (p = extra_tokens; p != NULL && *p != NULL; p += 2) {
410         if (strncmp(token + 2, *p, (token_end - token) - 2) == 0)
411             return expand_extra_token(context, p[1], ret);
412     }
413
414     for (i = 0; i < sizeof(tokens) / sizeof(tokens[0]); i++) {
415         if (!strncmp(token + 2, tokens[i].tok, (token_end - token) - 2)) {
416             return tokens[i].exp_func(context, tokens[i].param,
417                                       tokens[i].postfix, ret);
418         }
419     }
420
421     k5_setmsg(context, EINVAL, _("Invalid token"));
422     return EINVAL;
423 }
424
425 /*
426  * Expand tokens in path_in to produce *path_out.  The caller should free
427  * *path_out with free().
428  */
429 krb5_error_code
430 k5_expand_path_tokens(krb5_context context, const char *path_in,
431                       char **path_out)
432 {
433     return k5_expand_path_tokens_extra(context, path_in, path_out, NULL);
434 }
435
436 static void
437 free_extra_tokens(char **extra_tokens)
438 {
439     char **p;
440
441     for (p = extra_tokens; p != NULL && *p != NULL; p++)
442         free(*p);
443     free(extra_tokens);
444 }
445
446 /*
447  * Expand tokens in path_in to produce *path_out.  Arguments after path_out are
448  * pairs of extra token names and replacement values, terminated by a NULL.
449  * The caller should free *path_out with free().
450  */
451 krb5_error_code
452 k5_expand_path_tokens_extra(krb5_context context, const char *path_in,
453                             char **path_out, ...)
454 {
455     krb5_error_code ret;
456     struct k5buf buf;
457     char *tok_begin, *tok_end, *tok_val, **extra_tokens = NULL;
458     const char *path_left;
459     size_t nargs = 0, i;
460     va_list ap;
461
462     *path_out = NULL;
463
464     k5_buf_init_dynamic(&buf);
465
466     /* Count extra tokens. */
467     va_start(ap, path_out);
468     while (va_arg(ap, const char *) != NULL)
469         nargs++;
470     va_end(ap);
471     if (nargs % 2 != 0)
472         return EINVAL;
473
474     /* Get extra tokens. */
475     if (nargs > 0) {
476         extra_tokens = k5calloc(nargs + 1, sizeof(char *), &ret);
477         if (extra_tokens == NULL)
478             goto cleanup;
479         va_start(ap, path_out);
480         for (i = 0; i < nargs; i++) {
481             extra_tokens[i] = strdup(va_arg(ap, const char *));
482             if (extra_tokens[i] == NULL) {
483                 ret = ENOMEM;
484                 va_end(ap);
485                 goto cleanup;
486             }
487         }
488         va_end(ap);
489     }
490
491     path_left = path_in;
492     while (TRUE) {
493         /* Find the next token in path_left and add the literal text up to it.
494          * If there are no more tokens, we can finish up. */
495         tok_begin = strstr(path_left, "%{");
496         if (tok_begin == NULL) {
497             k5_buf_add(&buf, path_left);
498             break;
499         }
500         k5_buf_add_len(&buf, path_left, tok_begin - path_left);
501
502         /* Find the end of this token. */
503         tok_end = strchr(tok_begin, '}');
504         if (tok_end == NULL) {
505             ret = EINVAL;
506             k5_setmsg(context, ret, _("variable missing }"));
507             goto cleanup;
508         }
509
510         /* Expand this token and add its value. */
511         ret = expand_token(context, tok_begin, tok_end, extra_tokens,
512                            &tok_val);
513         if (ret)
514             goto cleanup;
515         k5_buf_add(&buf, tok_val);
516         free(tok_val);
517         path_left = tok_end + 1;
518     }
519
520     ret = k5_buf_status(&buf);
521     if (ret)
522         goto cleanup;
523
524 #ifdef _WIN32
525     /* Also deal with slashes. */
526     {
527         char *p;
528         for (p = buf.data; *p != '\0'; p++) {
529             if (*p == '/')
530                 *p = '\\';
531         }
532     }
533 #endif
534     *path_out = buf.data;
535     memset(&buf, 0, sizeof(buf));
536
537 cleanup:
538     k5_buf_free(&buf);
539     free_extra_tokens(extra_tokens);
540     return ret;
541 }