view common/util.c @ 1092:02fd9992ccc9

Added tag 0.9.2 for changeset 0ae3c99b82ee
author Andre Heinecke <andre.heinecke@intevation.de>
date Fri, 12 Sep 2014 16:11:08 +0200
parents f110a3f6e387
children fd85a02d771d
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>
#include <accctrl.h>
#include <aclapi.h>
#include <shlobj.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

#ifdef WIN32
bool
has_high_integrity(HANDLE hToken)
{
  PTOKEN_MANDATORY_LABEL integrity_label = NULL;
  DWORD integrity_level = 0,
        size = 0;

  if (hToken == NULL || hToken == INVALID_HANDLE_VALUE)
    {
      DEBUGPRINTF ("Invalid parameters.");
      return false;
    }

  /* Get the required size */
  if (!GetTokenInformation(hToken, TokenIntegrityLevel,
                           NULL, 0, &size) == ERROR_INSUFFICIENT_BUFFER)
    {
      PRINTLASTERROR ("Failed to get required size.\n");
      return false;
    }
  integrity_label = (PTOKEN_MANDATORY_LABEL) LocalAlloc(0, size);
  if (integrity_label == NULL)
    {
      ERRORPRINTF ("Failed to allocate label. \n");
      return false;
    }

  if (!GetTokenInformation(hToken, TokenIntegrityLevel,
                           integrity_label, size, &size))
    {
      PRINTLASTERROR ("Failed to get integrity level.\n");
      LocalFree(integrity_label);
      return false;
    }

  /* Get the last integrity level */
  integrity_level = *GetSidSubAuthority(integrity_label->Label.Sid,
                     (DWORD)(UCHAR)(*GetSidSubAuthorityCount(
                        integrity_label->Label.Sid) - 1));

  LocalFree (integrity_label);

  return integrity_level >= SECURITY_MANDATORY_HIGH_RID;
}
#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;
        }
    }
  /* Elevation will be true and ElevationType TokenElevationTypeFull even
     if the token is a user token created by SAFER so we additionally
     check the integrity level of the token which will only be high in
     the real elevated process and medium otherwise. */

  ret = ret && has_high_integrity (hToken);

  if (hToken)
    CloseHandle (hToken);
#endif
  return ret;
}

#ifdef _WIN32
char *
get_program_files_folder ()
{
  wchar_t *folder_name = NULL;
  char *retval = NULL;
  if (SHGetKnownFolderPath (&FOLDERID_ProgramFiles, /* Get program data dir */
                            KF_FLAG_NO_ALIAS,
                            INVALID_HANDLE_VALUE, /* Get it for the default user */
                            &folder_name) != S_OK)
    {
      PRINTLASTERROR ("Failed to get program files folder.");
      return NULL;
    }

  retval = wchar_to_utf8 (folder_name, wcslen(folder_name));
  CoTaskMemFree (folder_name);
  return retval;
}

/* This is a bit ridicoulous but necessary as shlobj.h contains an inline
   definition. So only one C file may include it and thus we have to put
   all our shlobj calls into one file... */
wchar_t *
get_program_data_folder ()
{
  wchar_t *folder_name = NULL;
  if (SHGetKnownFolderPath (&FOLDERID_ProgramData, /* Get program data dir */
                                 KF_FLAG_CREATE | /* Create if it does not exist */
                                 KF_FLAG_INIT, /* Initialize it if created */
                                 INVALID_HANDLE_VALUE, /* Get it for the default user */
                                 &folder_name) != S_OK)
    {
      PRINTLASTERROR ("Failed to get folder path");
      return NULL;
    }
  return folder_name;
}
#endif

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
}

#ifdef WIN32
bool
create_restricted_directory (LPWSTR path, bool objects_should_inherit)
{
  bool retval = false;
  PSID everyone_SID = NULL,
       admin_SID = NULL;
  PACL access_control_list = NULL;
  PSECURITY_DESCRIPTOR descriptor = NULL;
  EXPLICIT_ACCESS explicit_access[2];
  SID_IDENTIFIER_AUTHORITY world_identifier = {SECURITY_WORLD_SID_AUTHORITY},
                           admin_identifier = {SECURITY_NT_AUTHORITY};
  SECURITY_ATTRIBUTES security_attributes;

  ZeroMemory(&security_attributes, sizeof(security_attributes));
  ZeroMemory(&explicit_access, 2 * sizeof(EXPLICIT_ACCESS));

  /* Create a well-known SID for the Everyone group. */
  if(!AllocateAndInitializeSid(&world_identifier, /* top-level identifier */
                               1, /* subauthorties count */
                               SECURITY_WORLD_RID, /* Only one authority */
                               0, 0, 0, 0, 0, 0, 0, /* No other authorities*/
                               &everyone_SID))
    {
      PRINTLASTERROR ("Failed to allocate world sid.\n");
      return false;
    }

  /* Initialize the first EXPLICIT_ACCESS structure for an ACE.
     to allow everyone read access */
  explicit_access[0].grfAccessPermissions = GENERIC_READ; /* Give read access */
  explicit_access[0].grfAccessMode = SET_ACCESS; /* Overwrite other access for all users */
  explicit_access[0].grfInheritance = objects_should_inherit ?
                                      SUB_CONTAINERS_AND_OBJECTS_INHERIT : /* make it stick */
                                      NO_PROPAGATE_INHERIT_ACE; /* Don't inherit */
  explicit_access[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
  explicit_access[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
  explicit_access[0].Trustee.ptstrName  = (LPTSTR) everyone_SID;

  /* Create the SID for the BUILTIN\Administrators group. */
  if(!AllocateAndInitializeSid(&admin_identifier,
                               2,
                               SECURITY_BUILTIN_DOMAIN_RID, /*BUILTIN\ */
                               DOMAIN_ALIAS_RID_ADMINS, /*\Administrators */
                               0, 0, 0, 0, 0, 0, /* No other */
                               &admin_SID))
    {
      PRINTLASTERROR ("Failed to allocate admin sid.");
      goto done;
    }

  /* explicit_access[1] grants admins full rights for this object and inherits
     it to the children */
  explicit_access[1].grfAccessPermissions = GENERIC_ALL;
  explicit_access[1].grfAccessMode = SET_ACCESS;
  explicit_access[1].grfInheritance = objects_should_inherit ?
                                      SUB_CONTAINERS_AND_OBJECTS_INHERIT : /* make it stick */
                                      NO_PROPAGATE_INHERIT_ACE; /* Don't inherit */
  explicit_access[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
  explicit_access[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
  explicit_access[1].Trustee.ptstrName = (LPTSTR) admin_SID;

  /* Set up the ACL structure. */
  if (ERROR_SUCCESS != SetEntriesInAcl(2, explicit_access, NULL, &access_control_list))
    {
      PRINTLASTERROR ("Failed to set up Acl.");
      goto done;
    }

  /* Initialize a security descriptor */
  descriptor = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
               SECURITY_DESCRIPTOR_MIN_LENGTH);
  if (descriptor == NULL)
    {
      PRINTLASTERROR("Failed to allocate descriptor.");
      goto done;
    }

  if (!InitializeSecurityDescriptor(descriptor,
                                    SECURITY_DESCRIPTOR_REVISION))
    {
      PRINTLASTERROR("Failed to initialize descriptor.");
      goto done;
    }

  /* Now we add the ACL to the the descriptor */
  if (!SetSecurityDescriptorDacl(descriptor,
                                 TRUE,     /* bDaclPresent flag */
                                 access_control_list,
                                 FALSE))   /* not a default DACL */
    {
      PRINTLASTERROR("Failed to set security descriptor.");
      goto done;
    }

  /* Finally set up the security attributes structure */
  security_attributes.nLength = sizeof (SECURITY_ATTRIBUTES);
  security_attributes.lpSecurityDescriptor = descriptor;
  security_attributes.bInheritHandle = FALSE;

  /* Use the security attributes to create the directory */
  if (!CreateDirectoryW(path, &security_attributes))
    {
      DWORD err = GetLastError();
      if (err == ERROR_ALREADY_EXISTS)
        {
          /* Verify that the directory has the correct rights */
          // TODO
          retval = true;
          goto done;
        }
      ERRORPRINTF ("Failed to create directory. Err: %lu", err);
    }
  retval = true;

done:

  if (everyone_SID)
    FreeSid(everyone_SID);
  if (admin_SID)
    FreeSid(admin_SID);
  if (access_control_list)
    LocalFree(access_control_list);
  if (descriptor)
    LocalFree(descriptor);

  return retval;
}
#endif

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