Home | History | Annotate | Line # | Download | only in krb5
      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 
     39 typedef 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  */
     56 static 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 
     83 extern 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  */
     93 static 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  */
    143 static 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 
    241 static 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 
    270 static 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 
    279 static 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 
    296 static 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 
    312 static 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 
    327 static 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 
    337 static 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 
    379 static 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  */
    426 KRB5_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 
    435 static void
    436 free_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  */
    461 KRB5_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