view common/util.c @ 975:b3695a3399de

(issue86) Install into default directories on Linux If the mozilla process is now started as root it will try to write into the default directories for NSS Shared and mozilla / thunderbird profiles. Cinst will now start the mozilla process once as root.
author Andre Heinecke <andre.heinecke@intevation.de>
date Fri, 29 Aug 2014 12:59:44 +0200
parents 698b6a9bd75e
children 427e2e18b8c8
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "util.h"
#include "logging.h"
#include "strhelp.h"

#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
#else
#include <windows.h>
#endif

#ifndef APPNAME
#define APPNAME "TrustBridge"
#endif

#ifdef WIN32
char*
read_registry_string (const HKEY root, const wchar_t *key,
                      const wchar_t *name)
{
  HKEY key_handle = NULL;
  DWORD size = 0,
        type = 0,
        ex_size = 0,
        dwRet = 0;
  LONG ret = 0;
  char *retval = NULL;
  wchar_t *buf = NULL,
           *ex_buf = NULL;
  if (root == NULL || key == NULL || name == NULL)
    {
      ERRORPRINTF ("Invalid call to read_registry_string");
      return NULL;
    }

  ret = RegOpenKeyExW (root, key, 0, KEY_READ, &key_handle);
  if (ret != ERROR_SUCCESS)
    {
      ERRORPRINTF ("Failed to open key.");
      return NULL;
    }

  /* Get the size */
  ret = RegQueryValueExW (key_handle, name, 0, NULL, NULL, &size);
  if (ret != ERROR_MORE_DATA && !(ret == ERROR_SUCCESS && size != 0))
    {
      ERRORPRINTF ("Failed to get required registry size.");
      return retval;
    }

  /* Size is size in bytes not in characters */
  buf = xmalloc (size + sizeof(wchar_t));

  /* If the stored value is not zero terminated the returned value also
     is not zero terminated. That's why we reserve more and ensure it's
     initialized. */
  memset (buf, 0, size + sizeof(wchar_t));

  ret = RegQueryValueExW (key_handle, name, 0, &type, (LPBYTE) buf, &size);
  if (ret != ERROR_SUCCESS)
    {
      ERRORPRINTF ("Failed get registry value.");
      return retval;
    }

  if (type == REG_SZ || (type == REG_EXPAND_SZ && wcschr (buf, '%') == NULL))
    {
      /* Nothing to expand, we are done */
      retval = wchar_to_utf8 (buf, wcslen (buf));
      goto done;
    }

  if (type != REG_EXPAND_SZ)
    {
      ERRORPRINTF ("Unhandled registry type %i", type);
      goto done;
    }

  /* Expand the registry string */
  ex_size = ExpandEnvironmentStringsW (buf, NULL, 0);

  if (ex_size == 0)
    {
      PRINTLASTERROR ("Failed to determine expanded environment size.");
      goto done;
    }

  ex_buf = xmalloc ((ex_size + 1) * sizeof(wchar_t));

  dwRet = ExpandEnvironmentStringsW (buf, ex_buf, ex_size);

  ex_buf[ex_size] = '\0'; /* Make sure it's a string */

  if (dwRet == 0 || dwRet != ex_size)
    {
      PRINTLASTERROR ("Failed to expand environment variables.");
      goto done;
    }

  retval = wchar_to_utf8 (ex_buf, ex_size);

done:
  xfree (ex_buf);
  xfree (buf);

  RegCloseKey (key_handle);
  return retval;
}


/** @brief Compare two paths for equality based on the filename.
  *
  * Expand the paths by using GetFullPathName and do a string
  * comparison on the result to check for equality.
  *
  * To be sure if it is really the same file it would be better
  * to open the files and compare the serial number but this
  * suffices for checks that only impact on the options presented
  * to the user (try a system wide installation or not)
  *
  * If one file does not exist the function returns false. If
  * The path is longer then MAX_PATH this function also returns
  * false.
  *
  * @param [in] path1 first path to compare
  * @paran [in] path2 first path to compare
  * @returns true if the paths are the same.
  */
bool
paths_equal (const char *path1, const char *path2)
{
  bool ret = false;
  wchar_t buf1[MAX_PATH],
          buf2[MAX_PATH];
  wchar_t *wpath1 = NULL,
           *wpath2 = NULL;
  DWORD retval = 0;

  if (!path1 || !path2)
    {
      return false;
    }

  wpath1 = utf8_to_wchar(path1, strnlen(path1, MAX_PATH));
  wpath2 = utf8_to_wchar(path2, strnlen(path2, MAX_PATH));

  if (wpath1 == NULL || wpath2 == NULL)
    {
      ERRORPRINTF ("Failed to convert paths to wchar.");
      goto done;
    }

  retval = GetFullPathNameW (wpath1, MAX_PATH, buf1, NULL);
  if (retval >= MAX_PATH || retval != wcsnlen (buf1, MAX_PATH))
    {
      ERRORPRINTF ("Path1 too long.");
      goto done;
    }
  if (retval == 0)
    {
      PRINTLASTERROR ("Failed to get Full Path name.");
      goto done;
    }

  retval = GetFullPathNameW (wpath2, MAX_PATH, buf2, NULL);
  if (retval >= MAX_PATH || retval != wcsnlen (buf2, MAX_PATH))
    {
      ERRORPRINTF ("Path2 too long.");
      goto done;
    }
  if (retval == 0)
    {
      PRINTLASTERROR ("Failed to get Full Path name.");
      goto done;
    }

  ret = wcscmp (buf1, buf2) == 0;
done:
  xfree (wpath1);
  xfree (wpath2);

  return ret;
}

char *
get_install_dir()
{
  wchar_t wPath[MAX_PATH];
  char *utf8path = NULL;
  char *dirsep = NULL;

  if (!GetModuleFileNameW (NULL, wPath, MAX_PATH - 1))
    {
      PRINTLASTERROR ("Failed to obtain module file name. Path too long?");
      return NULL;
    }

  /* wPath might not be 0 terminated */
  wPath[MAX_PATH - 1] = '\0';

  utf8path = wchar_to_utf8 (wPath, wcsnlen(wPath, MAX_PATH));

  if (utf8path == NULL)
    {
      ERRORPRINTF ("Failed to convert module path to utf-8");
      return NULL;
    }

  /* Cut away the executable name */
  dirsep = strrchr(utf8path, '\\');
  if (dirsep == NULL)
    {
      ERRORPRINTF ("Failed to find directory seperator.");
      return NULL;
    }
  *dirsep = '\0';
  return utf8path;
}

static PSID
copy_sid(PSID from)
{
  if (!from)
    {
      return 0;
    }

  DWORD sidLength = GetLengthSid(from);
  PSID to = (PSID) xmalloc(sidLength);
  CopySid(sidLength, to, from);
  return to;
}

PSID
get_process_owner(HANDLE hProcess)
{
  HANDLE hToken = NULL;
  PSID sid;

  if (hProcess == NULL)
    {
      ERRORPRINTF ("invalid call to get_process_owner");
      return NULL;
    }

  OpenProcessToken(hProcess, TOKEN_READ, &hToken);
  if (hToken)
    {
      DWORD size = 0;
      PTOKEN_USER userStruct;

      // check how much space is needed
      GetTokenInformation(hToken, TokenUser, NULL, 0, &size);
      if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
        {
          userStruct = (PTOKEN_USER) xmalloc (size);
          GetTokenInformation(hToken, TokenUser, userStruct, size, &size);

          sid = copy_sid(userStruct->User.Sid);
          CloseHandle(hToken);
          xfree (userStruct);
          return sid;
        }
    }
  return NULL;
}

bool
is_system_install()
{
  char *reg_inst_dir = NULL,
        *real_prefix = NULL;
  bool ret = false;

  reg_inst_dir = read_registry_string (HKEY_LOCAL_MACHINE,
                                       L"Software\\"APPNAME, L"");

  if (reg_inst_dir == NULL)
    {
      return false;
    }
  DEBUGPRINTF ("Registered installation directory: %s\n", reg_inst_dir);

  real_prefix = get_install_dir();

  if (!real_prefix)
    {
      DEBUGPRINTF ("Failed to obtain installation prefix.");
      xfree (reg_inst_dir);
      return false;
    }

  ret = paths_equal (real_prefix, reg_inst_dir);

  xfree (real_prefix);
  xfree (reg_inst_dir);
  DEBUGPRINTF ("Is system install? %s\n", ret ? "true" : "false");
  return ret;
}
#else /* WIN32 */

char *
get_install_dir()
{
  char *retval = NULL,
        *p = NULL,
         buf[MAX_PATH_LINUX];
  ssize_t ret;
  size_t path_len = 0;

  ret = readlink ("/proc/self/exe", buf, MAX_PATH_LINUX);
  if (ret <= 0)
    {
      ERRORPRINTF ("readlink failed\n");
      return NULL;
    }

  buf[ret] = '\0';

  /* cut off the filename */
  p = strrchr (buf, '/');
  if (p == NULL)
    {
      ERRORPRINTF ("No filename found.\n");
      return NULL;
    }
  *(p + 1) = '\0';

  path_len = strlen (buf);
  retval = xmalloc (path_len + 1);
  strncpy (retval, buf, path_len);
  retval[path_len] = '\0';

  return retval;
}

bool
is_system_install()
{
  FILE *system_config;
  int read_lines = 0;
  char linebuf[MAX_PATH_LINUX + 7],
       * inst_dir = NULL;
  bool retval = false;
  size_t inst_dir_len = 0;

  system_config = fopen ("/etc/"APPNAME"/"APPNAME"-inst.cfg", "r");
  if (system_config == NULL)
    {
      DEBUGPRINTF ("No system wide install configuration found.\n");
      return false;
    }
  inst_dir = get_install_dir ();

  if (inst_dir == NULL)
    {
      ERRORPRINTF ("Failed to find installation directory.\n");
      fclose(system_config);
      return false;
    }

  inst_dir_len = strnlen (inst_dir, MAX_PATH_LINUX);

  if (inst_dir_len == 0 || inst_dir_len >= MAX_PATH_LINUX)
    {
      ERRORPRINTF ("Installation directory invalid.\n");
      fclose(system_config);
      return false;
    }

  /* Read the first 10 lines and look for PREFIX. if it is not found
     we return false. */
  while (read_lines < 10 && fgets (linebuf, MAX_PATH_LINUX + 7,
                                   system_config) != NULL)
    {
      if (str_starts_with (linebuf, "PREFIX="))
        {
          /* The last character is always a linebreak in a valid system_config
          file so we can strip it. If this is not true the file is invalid.
          linebuf is > 7 atm otherwise prefix= would not have been matched. */
          linebuf[strlen(linebuf) - 1] = '\0';
          retval = str_starts_with (inst_dir, linebuf + 7);
          break;
        }
      read_lines++;
    }

  fclose (system_config);
  xfree (inst_dir);
  DEBUGPRINTF ("Is system install? %s\n", retval ? "true" : "false");
  return retval;
}
#endif

bool
is_elevated()
{
  bool ret = false;
#ifndef _WIN32
  ret = (geteuid() == 0);
#else
  HANDLE hToken = NULL;
  if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken))
    {
      DWORD elevation;
      DWORD cbSize = sizeof (DWORD);
      if (GetTokenInformation (hToken, TokenElevation, &elevation,
                               sizeof (TokenElevation), &cbSize))
        {
          ret = elevation;
        }
    }
  if (hToken)
    CloseHandle (hToken);
#endif
  return ret;
}

bool
is_admin()
{
#ifndef _WIN32
  struct passwd *current_user = getpwuid (geteuid());
  int ngroups = 0,
      ret = 0,
      i = 0;
  gid_t * groups = NULL;

  if (current_user == NULL)
    {
      ERRORPRINTF ("Failed to obtain user information.");
      return false;
    }

  ret = getgrouplist (current_user->pw_name, current_user->pw_gid, NULL,
                      &ngroups);

  if (ret != -1 || ngroups <= 0)
    {
      ERRORPRINTF ("Unknown error in getgrouplist call");
      return false;
    }

  groups = xmalloc (((unsigned int)ngroups) * sizeof (gid_t));

  ret = getgrouplist (current_user->pw_name, current_user->pw_gid, groups,
                      &ngroups);

  if (ret != ngroups)
    {
      ERRORPRINTF ("Group length mismatch.");
      xfree (groups);
      return false;
    }

  for (i = 0; i < ngroups; i++)
    {
      struct group *gr = getgrgid (groups[i]);
      if (gr == NULL)
        {
          ERRORPRINTF ("Error in group enumeration");
          xfree (groups);
          return false;
        }
      if (strcmp("sudo", gr->gr_name) == 0)
        {
          DEBUGPRINTF ("User is in sudo group \n");
          xfree (groups);
          return true;
        }
    }

  DEBUGPRINTF ("User is not in sudo group");

  return false;
#else
  bool retval = false;
  BOOL in_admin_group = FALSE;
  HANDLE hToken = NULL;
  HANDLE hTokenToCheck = NULL;
  DWORD cbSize = 0;
  TOKEN_ELEVATION_TYPE elevation;
  BYTE admin_id[SECURITY_MAX_SID_SIZE];

  if (!OpenProcessToken(GetCurrentProcess(),
                        TOKEN_QUERY | TOKEN_DUPLICATE, &hToken))
    {
      PRINTLASTERROR ("Failed to duplicate process token.\n");
      return false;
    }

  if (!GetTokenInformation(hToken, TokenElevationType, &elevation,
                           sizeof(elevation), &cbSize))
    {
      PRINTLASTERROR ("Failed to get token information.\n");
      goto done;
    }

  /* If limited check the the linked token instead */
  if (TokenElevationTypeLimited == elevation)
    {
      if (!GetTokenInformation(hToken, TokenLinkedToken, &hTokenToCheck,
                               sizeof(hTokenToCheck), &cbSize))
        {
          PRINTLASTERROR ("Failed to get the linked token.\n");
          goto done;
        }
    }

  if (!hTokenToCheck) /* The linked token is already of the correct type */
    {
      if (!DuplicateToken(hToken, SecurityIdentification, &hTokenToCheck))
        {
          PRINTLASTERROR ("Failed to duplicate token for identification.\n");
          goto done;
        }
    }

  /* Do the sid dance for the adminSID */
  cbSize = sizeof(admin_id);
  if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &admin_id,
                          &cbSize))
    {
      PRINTLASTERROR ("Failed to get admin sid.\n");
      goto done;
    }

  /* The actual check */
  if (!CheckTokenMembership(hTokenToCheck, &admin_id, &in_admin_group))
    {
      PRINTLASTERROR ("Failed to check token membership.\n");
      goto done;
    }

  if (in_admin_group)
    {
      /* Winbool to standard bool */
      retval = true;
    }

done:
  if (hToken) CloseHandle(hToken);
  if (hTokenToCheck) CloseHandle(hTokenToCheck);

  return retval;
#endif
}

http://wald.intevation.org/projects/trustbridge/