Mercurial > trustbridge
view common/util.c @ 1225:15b92e88124d
Added AUTHORS file.
author | Emanuel Schuetze <emanuel@intevation.de> |
---|---|
date | Wed, 24 Sep 2014 14:56:30 +0200 |
parents | 0a803c3fb5a6 |
children | 845048d4a69f |
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 <winsafer.h> #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, PACL *rACL) { 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) { if (!objects_should_inherit) { /* This means it is a parent directory of something and we should not touch the DACL. */ retval = true; goto done; } /* Set our ACL on the directory */ err = SetNamedSecurityInfoW (path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, admin_SID, /* owner */ admin_SID, /* group */ access_control_list, /* the dacl */ NULL); if (err != ERROR_SUCCESS) { ERRORPRINTF ("Failed to set security info on folder. Err: %lu", err); goto done; } } else { ERRORPRINTF ("Failed to create directory. Err: %lu", err); goto done; } } retval = true; done: if (retval != true) { ERRORPRINTF ("Failed to create directory for NSS installer instructions."); syslog_error_printf ("Failed to create directory for NSS installer instructions."); } else if (rACL) { *rACL = access_control_list; } if (everyone_SID) FreeSid(everyone_SID); if (admin_SID) FreeSid(admin_SID); if (!rACL && access_control_list) LocalFree(access_control_list); if (descriptor) LocalFree(descriptor); return retval; } #endif #ifdef WIN32 /** @brief get a restricted access token * * This function uses the Software Restriction API to obtain the * access token for a process run als normal user. * * @returns A restricted handle or NULL on error. */ HANDLE get_restricted_token() { SAFER_LEVEL_HANDLE user_level = NULL; HANDLE retval = NULL; SID_IDENTIFIER_AUTHORITY medium_identifier = {SECURITY_MANDATORY_LABEL_AUTHORITY}; PSID medium_sid = NULL; TOKEN_MANDATORY_LABEL integrity_label; memset (&integrity_label, 0, sizeof (integrity_label)); if (!SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &user_level, NULL)) { PRINTLASTERROR ("Failed to create user level.\n"); return NULL; } if (!SaferComputeTokenFromLevel(user_level, NULL, &retval, 0, NULL)) { SaferCloseLevel(user_level); return NULL; } SaferCloseLevel(user_level); /* Set the SID to medium it will still be high otherwise. Even if there is no high access allowed. */ if (!AllocateAndInitializeSid(&medium_identifier, 1, SECURITY_MANDATORY_MEDIUM_RID, 0, 0, 0, 0, 0, 0, 0, &medium_sid)) { PRINTLASTERROR ("Failed to initialize sid.\n"); return NULL; } integrity_label.Label.Attributes = SE_GROUP_INTEGRITY; integrity_label.Label.Sid = medium_sid; if (!SetTokenInformation(retval, TokenIntegrityLevel, &integrity_label, sizeof(TOKEN_MANDATORY_LABEL))) { PRINTLASTERROR ("Failed to set token integrity.\n"); return NULL; } return retval; } #endif