view common/util.c @ 1070:f110a3f6e387

(issue114) Fine tune ACL propagation using mkdir_p the ACL of the parent directories would propagate to all subdirectories and objects in the directory. Now we only use ACL propagation in the last directory to make sure that files we might create in that directory inherit the correct (resitricted) ACL
author Andre Heinecke <andre.heinecke@intevation.de>
date Wed, 10 Sep 2014 16:41:36 +0200
parents 1f23803e1f83
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/