view common/util.c @ 841:216a65d7fc4b

(issue66) Implement is_system_install and use it This has completly different implementations for linux and Windows. The commit also moves some code into util.c for better reuse.
author Andre Heinecke <andre.heinecke@intevation.de>
date Tue, 29 Jul 2014 18:12:57 +0200
parents 4ad764bfb39c
children f89b41fa7048
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/