aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik aheinecke@404: * Software engineering by Intevation GmbH aheinecke@404: * aheinecke@404: * This file is Free Software under the GNU GPL (v>=2) aheinecke@404: * and comes with ABSOLUTELY NO WARRANTY! aheinecke@404: * See LICENSE.txt for details. aheinecke@404: */ aheinecke@321: #include "util.h" aheinecke@505: #include "logging.h" andre@644: #include "strhelp.h" aheinecke@505: wilde@323: #ifndef _WIN32 wilde@323: #include wilde@323: #include andre@644: #include andre@644: #include andre@644: #include wilde@323: #else aheinecke@321: #include aheinecke@321: #endif aheinecke@321: andre@841: #ifndef APPNAME andre@841: #define APPNAME "TrustBridge" andre@841: #endif andre@841: andre@671: #ifdef WIN32 andre@841: char* andre@841: read_registry_string (const HKEY root, const wchar_t *key, andre@841: const wchar_t *name) andre@841: { andre@841: HKEY key_handle = NULL; andre@841: DWORD size = 0, andre@841: type = 0, andre@841: ex_size = 0, andre@841: dwRet = 0; andre@841: LONG ret = 0; andre@841: char *retval = NULL; andre@841: wchar_t *buf = NULL, andre@905: *ex_buf = NULL; andre@841: if (root == NULL || key == NULL || name == NULL) andre@841: { andre@841: ERRORPRINTF ("Invalid call to read_registry_string"); andre@841: return NULL; andre@841: } andre@841: andre@841: ret = RegOpenKeyExW (root, key, 0, KEY_READ, &key_handle); andre@841: if (ret != ERROR_SUCCESS) andre@841: { andre@841: ERRORPRINTF ("Failed to open key."); andre@841: return NULL; andre@841: } andre@841: andre@841: /* Get the size */ andre@841: ret = RegQueryValueExW (key_handle, name, 0, NULL, NULL, &size); andre@841: if (ret != ERROR_MORE_DATA && !(ret == ERROR_SUCCESS && size != 0)) andre@841: { andre@841: ERRORPRINTF ("Failed to get required registry size."); andre@841: return retval; andre@841: } andre@841: andre@841: /* Size is size in bytes not in characters */ andre@841: buf = xmalloc (size + sizeof(wchar_t)); andre@841: andre@841: /* If the stored value is not zero terminated the returned value also andre@841: is not zero terminated. That's why we reserve more and ensure it's andre@841: initialized. */ andre@841: memset (buf, 0, size + sizeof(wchar_t)); andre@841: andre@841: ret = RegQueryValueExW (key_handle, name, 0, &type, (LPBYTE) buf, &size); andre@841: if (ret != ERROR_SUCCESS) andre@841: { andre@841: ERRORPRINTF ("Failed get registry value."); andre@841: return retval; andre@841: } andre@841: andre@841: if (type == REG_SZ || (type == REG_EXPAND_SZ && wcschr (buf, '%') == NULL)) andre@841: { andre@841: /* Nothing to expand, we are done */ andre@841: retval = wchar_to_utf8 (buf, wcslen (buf)); andre@841: goto done; andre@841: } andre@841: andre@841: if (type != REG_EXPAND_SZ) andre@841: { andre@841: ERRORPRINTF ("Unhandled registry type %i", type); andre@841: goto done; andre@841: } andre@841: andre@841: /* Expand the registry string */ andre@841: ex_size = ExpandEnvironmentStringsW (buf, NULL, 0); andre@841: andre@841: if (ex_size == 0) andre@841: { andre@841: PRINTLASTERROR ("Failed to determine expanded environment size."); andre@841: goto done; andre@841: } andre@841: andre@841: ex_buf = xmalloc ((ex_size + 1) * sizeof(wchar_t)); andre@841: andre@841: dwRet = ExpandEnvironmentStringsW (buf, ex_buf, ex_size); andre@841: andre@841: ex_buf[ex_size] = '\0'; /* Make sure it's a string */ andre@841: andre@841: if (dwRet == 0 || dwRet != ex_size) andre@841: { andre@841: PRINTLASTERROR ("Failed to expand environment variables."); andre@841: goto done; andre@841: } andre@841: andre@841: retval = wchar_to_utf8 (ex_buf, ex_size); andre@841: andre@841: done: andre@841: xfree (ex_buf); andre@841: xfree (buf); andre@841: andre@841: RegCloseKey (key_handle); andre@841: return retval; andre@841: } andre@841: andre@841: andre@841: /** @brief Compare two paths for equality based on the filename. andre@841: * andre@841: * Expand the paths by using GetFullPathName and do a string andre@841: * comparison on the result to check for equality. andre@841: * andre@841: * To be sure if it is really the same file it would be better andre@841: * to open the files and compare the serial number but this andre@841: * suffices for checks that only impact on the options presented andre@841: * to the user (try a system wide installation or not) andre@841: * andre@841: * If one file does not exist the function returns false. If andre@841: * The path is longer then MAX_PATH this function also returns andre@841: * false. andre@841: * andre@841: * @param [in] path1 first path to compare andre@841: * @paran [in] path2 first path to compare andre@841: * @returns true if the paths are the same. andre@841: */ andre@841: bool andre@841: paths_equal (const char *path1, const char *path2) andre@841: { andre@905: bool ret = false; andre@905: wchar_t buf1[MAX_PATH], andre@905: buf2[MAX_PATH]; andre@905: wchar_t *wpath1 = NULL, andre@905: *wpath2 = NULL; andre@905: DWORD retval = 0; andre@841: andre@905: if (!path1 || !path2) andre@905: { andre@905: return false; andre@905: } andre@841: andre@905: wpath1 = utf8_to_wchar(path1, strnlen(path1, MAX_PATH)); andre@905: wpath2 = utf8_to_wchar(path2, strnlen(path2, MAX_PATH)); andre@841: andre@905: if (wpath1 == NULL || wpath2 == NULL) andre@905: { andre@905: ERRORPRINTF ("Failed to convert paths to wchar."); andre@905: goto done; andre@905: } andre@905: andre@905: retval = GetFullPathNameW (wpath1, MAX_PATH, buf1, NULL); andre@905: if (retval >= MAX_PATH || retval != wcsnlen (buf1, MAX_PATH)) andre@905: { andre@905: ERRORPRINTF ("Path1 too long."); andre@905: goto done; andre@905: } andre@905: if (retval == 0) andre@905: { andre@905: PRINTLASTERROR ("Failed to get Full Path name."); andre@905: goto done; andre@905: } andre@905: andre@905: retval = GetFullPathNameW (wpath2, MAX_PATH, buf2, NULL); andre@905: if (retval >= MAX_PATH || retval != wcsnlen (buf2, MAX_PATH)) andre@905: { andre@905: ERRORPRINTF ("Path2 too long."); andre@905: goto done; andre@905: } andre@905: if (retval == 0) andre@905: { andre@905: PRINTLASTERROR ("Failed to get Full Path name."); andre@905: goto done; andre@905: } andre@905: andre@905: ret = wcscmp (buf1, buf2) == 0; andre@841: done: andre@905: xfree (wpath1); andre@905: xfree (wpath2); andre@841: andre@905: return ret; andre@841: } andre@841: andre@841: char * andre@841: get_install_dir() andre@675: { andre@675: wchar_t wPath[MAX_PATH]; andre@675: char *utf8path = NULL; andre@675: char *dirsep = NULL; andre@675: andre@904: if (!GetModuleFileNameW (NULL, wPath, MAX_PATH - 1)) andre@675: { andre@675: PRINTLASTERROR ("Failed to obtain module file name. Path too long?"); andre@675: return NULL; andre@675: } andre@675: andre@675: /* wPath might not be 0 terminated */ andre@675: wPath[MAX_PATH - 1] = '\0'; andre@675: andre@675: utf8path = wchar_to_utf8 (wPath, wcsnlen(wPath, MAX_PATH)); andre@675: andre@675: if (utf8path == NULL) andre@675: { andre@675: ERRORPRINTF ("Failed to convert module path to utf-8"); andre@675: return NULL; andre@675: } andre@675: andre@675: /* Cut away the executable name */ andre@675: dirsep = strrchr(utf8path, '\\'); andre@675: if (dirsep == NULL) andre@675: { andre@675: ERRORPRINTF ("Failed to find directory seperator."); andre@675: return NULL; andre@675: } andre@675: *dirsep = '\0'; andre@675: return utf8path; andre@675: } andre@675: andre@670: static PSID andre@670: copy_sid(PSID from) andre@670: { andre@670: if (!from) andre@670: { andre@670: return 0; andre@670: } andre@670: andre@670: DWORD sidLength = GetLengthSid(from); andre@670: PSID to = (PSID) xmalloc(sidLength); andre@670: CopySid(sidLength, to, from); andre@670: return to; andre@670: } andre@670: andre@670: PSID andre@670: get_process_owner(HANDLE hProcess) andre@670: { andre@905: HANDLE hToken = NULL; andre@905: PSID sid; andre@670: andre@905: if (hProcess == NULL) andre@905: { andre@905: ERRORPRINTF ("invalid call to get_process_owner"); andre@905: return NULL; andre@905: } andre@670: andre@905: OpenProcessToken(hProcess, TOKEN_READ, &hToken); andre@905: if (hToken) andre@905: { andre@905: DWORD size = 0; andre@905: PTOKEN_USER userStruct; andre@670: andre@905: // check how much space is needed andre@905: GetTokenInformation(hToken, TokenUser, NULL, 0, &size); andre@905: if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) andre@905: { andre@905: userStruct = (PTOKEN_USER) xmalloc (size); andre@905: GetTokenInformation(hToken, TokenUser, userStruct, size, &size); andre@905: andre@905: sid = copy_sid(userStruct->User.Sid); andre@905: CloseHandle(hToken); andre@905: xfree (userStruct); andre@905: return sid; andre@905: } andre@905: } andre@905: return NULL; andre@670: } andre@841: andre@841: bool andre@841: is_system_install() andre@841: { andre@841: char *reg_inst_dir = NULL, andre@905: *real_prefix = NULL; andre@841: bool ret = false; andre@841: andre@841: reg_inst_dir = read_registry_string (HKEY_LOCAL_MACHINE, andre@841: L"Software\\"APPNAME, L""); andre@841: andre@841: if (reg_inst_dir == NULL) andre@841: { andre@841: return false; andre@841: } andre@841: DEBUGPRINTF ("Registered installation directory: %s\n", reg_inst_dir); andre@841: andre@841: real_prefix = get_install_dir(); andre@841: andre@841: if (!real_prefix) andre@841: { andre@841: DEBUGPRINTF ("Failed to obtain installation prefix."); andre@841: xfree (reg_inst_dir); andre@841: return false; andre@841: } andre@841: andre@841: ret = paths_equal (real_prefix, reg_inst_dir); andre@841: andre@841: xfree (real_prefix); andre@841: xfree (reg_inst_dir); andre@841: DEBUGPRINTF ("Is system install? %s\n", ret ? "true" : "false"); andre@841: return ret; andre@841: } andre@841: #else /* WIN32 */ andre@841: andre@841: char * andre@841: get_install_dir() andre@841: { andre@841: char *retval = NULL, andre@905: *p = NULL, andre@905: buf[MAX_PATH_LINUX]; andre@841: ssize_t ret; andre@841: size_t path_len = 0; andre@841: andre@841: ret = readlink ("/proc/self/exe", buf, MAX_PATH_LINUX); andre@841: if (ret <= 0) andre@841: { andre@841: ERRORPRINTF ("readlink failed\n"); andre@841: return NULL; andre@841: } andre@841: andre@841: buf[ret] = '\0'; andre@841: andre@841: /* cut off the filename */ andre@841: p = strrchr (buf, '/'); andre@841: if (p == NULL) andre@841: { andre@841: ERRORPRINTF ("No filename found.\n"); andre@841: return NULL; andre@841: } andre@841: *(p + 1) = '\0'; andre@841: andre@841: path_len = strlen (buf); andre@841: retval = xmalloc (path_len + 1); andre@841: strncpy (retval, buf, path_len); andre@841: retval[path_len] = '\0'; andre@841: andre@841: return retval; andre@841: } andre@841: andre@841: bool andre@841: is_system_install() andre@841: { andre@841: FILE *system_config; andre@841: int read_lines = 0; andre@841: char linebuf[MAX_PATH_LINUX + 7], andre@841: * inst_dir = NULL; andre@841: bool retval = false; andre@841: size_t inst_dir_len = 0; andre@841: andre@841: system_config = fopen ("/etc/"APPNAME"/"APPNAME"-inst.cfg", "r"); andre@841: if (system_config == NULL) andre@841: { andre@841: DEBUGPRINTF ("No system wide install configuration found.\n"); andre@841: return false; andre@841: } andre@841: inst_dir = get_install_dir (); andre@841: andre@841: if (inst_dir == NULL) andre@841: { andre@841: ERRORPRINTF ("Failed to find installation directory.\n"); andre@841: fclose(system_config); andre@841: return false; andre@841: } andre@841: andre@841: inst_dir_len = strnlen (inst_dir, MAX_PATH_LINUX); andre@841: andre@841: if (inst_dir_len == 0 || inst_dir_len >= MAX_PATH_LINUX) andre@841: { andre@841: ERRORPRINTF ("Installation directory invalid.\n"); andre@841: fclose(system_config); andre@841: return false; andre@841: } andre@841: andre@841: /* Read the first 10 lines and look for PREFIX. if it is not found andre@841: we return false. */ andre@841: while (read_lines < 10 && fgets (linebuf, MAX_PATH_LINUX + 7, andre@841: system_config) != NULL) andre@841: { andre@841: if (str_starts_with (linebuf, "PREFIX=")) andre@841: { andre@841: /* The last character is always a linebreak in a valid system_config andre@841: file so we can strip it. If this is not true the file is invalid. andre@841: linebuf is > 7 atm otherwise prefix= would not have been matched. */ andre@841: linebuf[strlen(linebuf) - 1] = '\0'; andre@841: retval = str_starts_with (inst_dir, linebuf + 7); andre@841: break; andre@841: } andre@841: read_lines++; andre@841: } andre@841: andre@841: fclose (system_config); andre@841: xfree (inst_dir); andre@841: DEBUGPRINTF ("Is system install? %s\n", retval ? "true" : "false"); andre@841: return retval; andre@841: } andre@671: #endif andre@670: aheinecke@321: bool wilde@323: is_elevated() wilde@323: { wilde@323: bool ret = false; wilde@323: #ifndef _WIN32 wilde@323: ret = (geteuid() == 0); wilde@323: #else wilde@323: HANDLE hToken = NULL; wilde@323: if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken)) wilde@323: { wilde@323: DWORD elevation; wilde@323: DWORD cbSize = sizeof (DWORD); wilde@323: if (GetTokenInformation (hToken, TokenElevation, &elevation, wilde@323: sizeof (TokenElevation), &cbSize)) wilde@323: { wilde@323: ret = elevation; wilde@323: } wilde@323: } wilde@323: if (hToken) wilde@323: CloseHandle (hToken); wilde@323: #endif wilde@323: return ret; wilde@323: } aheinecke@505: andre@841: bool andre@841: is_admin() aheinecke@505: { aheinecke@505: #ifndef _WIN32 andre@644: struct passwd *current_user = getpwuid (geteuid()); andre@644: int ngroups = 0, andre@644: ret = 0, andre@644: i = 0; andre@644: gid_t * groups = NULL; andre@644: andre@644: if (current_user == NULL) andre@644: { andre@644: ERRORPRINTF ("Failed to obtain user information."); andre@644: return false; andre@644: } andre@644: andre@644: ret = getgrouplist (current_user->pw_name, current_user->pw_gid, NULL, andre@644: &ngroups); andre@644: andre@644: if (ret != -1 || ngroups <= 0) andre@644: { andre@644: ERRORPRINTF ("Unknown error in getgrouplist call"); andre@644: return false; andre@644: } andre@644: andre@644: groups = xmalloc (((unsigned int)ngroups) * sizeof (gid_t)); andre@644: andre@644: ret = getgrouplist (current_user->pw_name, current_user->pw_gid, groups, andre@644: &ngroups); andre@644: andre@644: if (ret != ngroups) andre@644: { andre@644: ERRORPRINTF ("Group length mismatch."); andre@644: xfree (groups); andre@644: return false; andre@644: } andre@644: andre@644: for (i = 0; i < ngroups; i++) andre@644: { andre@644: struct group *gr = getgrgid (groups[i]); andre@644: if (gr == NULL) andre@644: { andre@644: ERRORPRINTF ("Error in group enumeration"); andre@644: xfree (groups); andre@644: return false; andre@644: } andre@644: if (strcmp("sudo", gr->gr_name) == 0) andre@644: { andre@644: DEBUGPRINTF ("User is in sudo group \n"); andre@644: xfree (groups); andre@644: return true; andre@644: } andre@644: } andre@644: andre@644: DEBUGPRINTF ("User is not in sudo group"); andre@644: aheinecke@505: return false; aheinecke@505: #else aheinecke@505: bool retval = false; aheinecke@505: BOOL in_admin_group = FALSE; aheinecke@505: HANDLE hToken = NULL; aheinecke@505: HANDLE hTokenToCheck = NULL; aheinecke@505: DWORD cbSize = 0; aheinecke@505: TOKEN_ELEVATION_TYPE elevation; aheinecke@505: BYTE admin_id[SECURITY_MAX_SID_SIZE]; aheinecke@505: aheinecke@505: if (!OpenProcessToken(GetCurrentProcess(), aheinecke@505: TOKEN_QUERY | TOKEN_DUPLICATE, &hToken)) aheinecke@505: { aheinecke@505: PRINTLASTERROR ("Failed to duplicate process token.\n"); aheinecke@505: return false; aheinecke@505: } aheinecke@505: aheinecke@505: if (!GetTokenInformation(hToken, TokenElevationType, &elevation, aheinecke@505: sizeof(elevation), &cbSize)) aheinecke@505: { aheinecke@505: PRINTLASTERROR ("Failed to get token information.\n"); aheinecke@505: goto done; aheinecke@505: } aheinecke@505: aheinecke@505: /* If limited check the the linked token instead */ aheinecke@505: if (TokenElevationTypeLimited == elevation) aheinecke@505: { aheinecke@505: if (!GetTokenInformation(hToken, TokenLinkedToken, &hTokenToCheck, aheinecke@505: sizeof(hTokenToCheck), &cbSize)) aheinecke@505: { aheinecke@505: PRINTLASTERROR ("Failed to get the linked token.\n"); aheinecke@505: goto done; aheinecke@505: } aheinecke@505: } aheinecke@505: aheinecke@505: if (!hTokenToCheck) /* The linked token is already of the correct type */ aheinecke@505: { aheinecke@505: if (!DuplicateToken(hToken, SecurityIdentification, &hTokenToCheck)) aheinecke@505: { aheinecke@505: PRINTLASTERROR ("Failed to duplicate token for identification.\n"); aheinecke@505: goto done; aheinecke@505: } aheinecke@505: } aheinecke@505: aheinecke@505: /* Do the sid dance for the adminSID */ aheinecke@505: cbSize = sizeof(admin_id); aheinecke@505: if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &admin_id, aheinecke@505: &cbSize)) aheinecke@505: { aheinecke@505: PRINTLASTERROR ("Failed to get admin sid.\n"); aheinecke@505: goto done; aheinecke@505: } aheinecke@505: aheinecke@505: /* The actual check */ aheinecke@505: if (!CheckTokenMembership(hTokenToCheck, &admin_id, &in_admin_group)) aheinecke@505: { aheinecke@505: PRINTLASTERROR ("Failed to check token membership.\n"); aheinecke@505: goto done; aheinecke@505: } aheinecke@505: aheinecke@505: if (in_admin_group) aheinecke@505: { aheinecke@505: /* Winbool to standard bool */ aheinecke@505: retval = true; aheinecke@505: } aheinecke@505: aheinecke@505: done: aheinecke@505: if (hToken) CloseHandle(hToken); aheinecke@505: if (hTokenToCheck) CloseHandle(hTokenToCheck); aheinecke@505: aheinecke@505: return retval; aheinecke@505: #endif aheinecke@505: } aheinecke@505: