/* AccessControl plugin for NSIS * Copyright (C) 2003 Mathias Hasselmann * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not * be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ #define WIN32_LEAN_AND_MEAN #include #include #include #include #include "../exdll/exdll.h" /***************************************************************************** GLOBAL VARIABLES *****************************************************************************/ #pragma message("Warning: Where is g_output?") //extern FILE * g_output; static HINSTANCE g_hInstance = NULL; static HWND g_hwndParent = NULL; static HWND g_hwndDialog = NULL; static HWND g_hwndProgress = NULL; static unsigned g_tempsize = 0; static char * g_temp = NULL; /***************************************************************************** TYPE DEFINITIONS *****************************************************************************/ typedef struct { const char * name; SE_OBJECT_TYPE type; BYTE defaultInheritance; const char ** const permissionNames; const DWORD * const permissionFlags; const int permissionCount; } SchemeType; typedef enum { ChangeMode_Owner, ChangeMode_Group } ChangeMode; /***************************************************************************** PLUG-IN HANDLING *****************************************************************************/ #define PUBLIC_FUNCTION(Name) \ void __declspec(dllexport) Name(HWND hwndParent, int string_size, \ char * variables, stack_t ** stacktop) \ { \ EXDLL_INIT(); \ AllocGlobalTemp(); \ g_hwndParent = hwndParent; \ g_hwndDialog = FindWindowEx(g_hwndParent, NULL, WC_DIALOG, ""); \ g_hwndProgress = GetDlgItem(g_hwndDialog, 1006); #define PUBLIC_FUNCTION_END \ } #define ABORT(Reason) \ do { ShowError Reason; goto cleanup; } while(0) /** Resizes the global character buffer g_temp if needed. Information about ** the need to do this is taken from g_stringsize. This function is useful ** to ensure each argument passed to this plugin's API can be copied to ** g_temp. **/ static void AllocGlobalTemp() { if (g_stringsize >= g_tempsize) { char * temp = (char *) LocalAlloc(LPTR, g_stringsize + 1); if (NULL != temp) { LocalFree((HLOCAL) g_temp); g_tempsize = g_stringsize + 1; g_temp = temp; } } } BOOL WINAPI _DllMainCRTStartup(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) { g_hInstance = hInst; return TRUE; } /***************************************************************************** ARRAY UTILITIES *****************************************************************************/ #define SIZE_OF_ARRAY(Array) \ (sizeof((Array)) / sizeof(*(Array))) #define ARRAY_CONTAINS(Array, Index) \ ArrayContainsImpl(Index, SIZE_OF_ARRAY(Array)) /** Checks if an array of a given size contains a given index. ** Used by the ARRAY_CONTAINS macro. **/ static int ArrayContainsImpl(int index, int size) { return index >= 0 && index < size; } /***************************************************************************** FEEDBACK UTILITIES *****************************************************************************/ static void ShowProgressV(const char * fmt, va_list args) { char progress[1024]; wvnsprintf(progress, sizeof(progress), fmt, args); SetWindowText(g_hwndProgress, progress); // fprintf(g_output, "%s\r\n", progress); } static void ShowProgress(const char * fmt, ...) { va_list args; va_start(args, fmt); ShowProgressV(fmt, args); va_end(args); } static void ShowErrorV(const char * fmt, va_list args) { char message[1024]; wvnsprintf(message, sizeof(message), fmt, args); MessageBox(g_hwndParent, message, NULL, MB_OK | MB_ICONERROR | MB_TOPMOST); pushstring(message); } static void ShowError(const char * fmt, ...) { va_list args; va_start(args, fmt); ShowErrorV(fmt, args); va_end(args); } /***************************************************************************** STRING UTILITIES *****************************************************************************/ /** Converts a string into an enumeration index. If the enumeration ** contains the string the index of the string within the enumeration ** is return. On error you'll receive -1. **/ static int ParseEnum(const char * keywords[], const char * str) { const char ** key; for(key = keywords; *key; ++key) if (!lstrcmpi(str, *key)) return (int)(key - keywords); return -1; } /** Parses a trustee string. If encloded in curlies the string contains ** a string SID. Otherwise it's assumed that the string contains a ** trustee name. The caller has to make sure to release the string ** returned with LocalFree. **/ static char * ParseTrustee(char * trustee, DWORD * trusteeForm) { char * strend = trustee + strlen(trustee) - 1; if ('(' == *trustee && ')' == *strend) { PSID pSid = NULL; *strend = '\0'; ++trustee; if (!ConvertStringSidToSid(trustee, &pSid)) pSid = NULL; *trusteeForm = TRUSTEE_IS_SID; return (char *) pSid; } *trusteeForm = TRUSTEE_IS_NAME; return StrDup(trustee); } static char * ParseSid(char * trustee) { PSID pSid = NULL; char * strend = trustee + strlen(trustee) - 1; if ('(' == *trustee && ')' == *strend) { *strend = '\0'; ++trustee; if (!ConvertStringSidToSid(trustee, &pSid)) pSid = NULL; } else { DWORD sidLen = 0; DWORD domLen = 0; char * domain = NULL; SID_NAME_USE use; if ((LookupAccountName(NULL, trustee, NULL, &sidLen, NULL, &domLen, &use) || ERROR_INSUFFICIENT_BUFFER == GetLastError()) && NULL != (domain = LocalAlloc(LPTR, domLen)) && NULL != (pSid = LocalAlloc(LPTR, sidLen))) { if (!LookupAccountName(NULL, trustee, pSid, &sidLen, domain, &domLen, &use)) { ShowError("uh.."); LocalFree(pSid); pSid = NULL; } } LocalFree(domain); } return pSid; } /* i know: this function is far to generious in accepting strings. * but hey: this all is about code size, isn't it? * so i can live with that pretty well. */ static DWORD ParsePermissions(const SchemeType * scheme, char * str) { DWORD perms = 0; char * first, * last; for(first = str; *first; first = last) { int token; while(*first && *first <= ' ') ++first; for(last = first; *last && *last > ' ' && *last != '|' && *last != '+'; ++last); if (*last) *last++ = '\0'; token = ParseEnum(scheme->permissionNames, first); if (token >= 0 && token < scheme->permissionCount) perms|= scheme->permissionFlags[token]; } return perms; } /***************************************************************************** SYMBOL TABLES *****************************************************************************/ static const char * g_filePermissionNames[] = { "ReadData", "WriteData", "AppendData", "ReadEA", "WriteEA", "Execute", "ReadAttributes", "WriteAttributes", "Delete", "ReadControl", "WriteDAC", "WriteOwner", "Synchronize", "FullAccess", "GenericRead", "GenericWrite", "GenericExecute", NULL }; static const DWORD g_filePermissionFlags[] = { FILE_READ_DATA, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_READ_EA, FILE_WRITE_EA, FILE_EXECUTE, FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE, FILE_ALL_ACCESS, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE }; static const char * g_directoryPermissionNames[] = { "ListDirectory", "AddFile", "AddSubdirectory", "ReadEA", "WriteEA", "Traverse", "DeleteChild", "ReadAttributes", "WriteAttributes", "Delete", "ReadControl", "WriteDAC", "WriteOwner", "Synchronize", "FullAccess", "GenericRead", "GenericWrite", "GenericExecute", NULL }; static const DWORD g_directoryPermissionFlags[] = { FILE_LIST_DIRECTORY, FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY, FILE_READ_EA, FILE_WRITE_EA, FILE_TRAVERSE, FILE_DELETE_CHILD, FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE, FILE_ALL_ACCESS, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE }; static const char * g_registryPermissionNames[] = { "QueryValue", "SetValue", "CreateSubKey", "EnumerateSubKeys", "Notify", "CreateLink", "Delete", "ReadControl", "WriteDAC", "WriteOwner", "Synchronize", "GenericRead", "GenericWrite", "GenericExecute", "FullAccess", NULL }; static const DWORD g_registryPermissionFlags[] = { KEY_QUERY_VALUE, KEY_SET_VALUE, KEY_CREATE_SUB_KEY, KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, KEY_CREATE_LINK, DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE, KEY_READ, KEY_WRITE, KEY_EXECUTE, KEY_ALL_ACCESS }; static const SchemeType g_fileScheme[] = { "file", SE_FILE_OBJECT, OBJECT_INHERIT_ACE, g_filePermissionNames, g_filePermissionFlags, SIZE_OF_ARRAY(g_filePermissionFlags) }; static const SchemeType g_directoryScheme[] = { "directory", SE_FILE_OBJECT, OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, g_directoryPermissionNames, g_directoryPermissionFlags, SIZE_OF_ARRAY(g_directoryPermissionFlags) }; static const SchemeType g_registryScheme[] = { "registry", SE_REGISTRY_KEY, CONTAINER_INHERIT_ACE, g_registryPermissionNames, g_registryPermissionFlags, SIZE_OF_ARRAY(g_registryPermissionFlags) }; static const char * g_rootKeyNames[] = { "HKCR", "HKCU", "HKLM", "HKU", NULL }; static const char * g_rootKeyPrefixes[] = { "CLASSES_ROOT\\", "CURRENT_USER\\", "MACHINE\\", "USERS\\" }; /***************************************************************************** GENERIC ACL HANDLING *****************************************************************************/ static void ChangeDACL(const SchemeType * scheme, char * path, DWORD mode) { char * trusteeName = NULL; DWORD trusteeForm = TRUSTEE_IS_NAME; DWORD permissions = 0; PACL pOldAcl = NULL; PACL pNewAcl = NULL; EXPLICIT_ACCESS access; if (popstring(g_temp)) ABORT(("Trustee is missing")); if (NULL == (trusteeName = ParseTrustee(g_temp, &trusteeForm))) ABORT(("Bad trustee (%s)", g_temp)); if (popstring(g_temp)) ABORT(("Permission flags are missing")); if (0 == (permissions = ParsePermissions(scheme, g_temp))) ABORT(("Bad permission flags (%s)", g_temp)); ShowProgress("Adjusting %s permissions for %s", scheme->name, path); if (ERROR_SUCCESS != GetNamedSecurityInfo(path, scheme->type, DACL_SECURITY_INFORMATION, NULL, NULL, &pOldAcl, NULL, NULL)) ABORT(("Cannot read access control list.\r\n" "Error code: %d", GetLastError())); BuildExplicitAccessWithName(&access, "", permissions, mode, scheme->defaultInheritance); access.Trustee.TrusteeForm = trusteeForm; access.Trustee.ptstrName = trusteeName; if (ERROR_SUCCESS != SetEntriesInAcl(1, &access, pOldAcl, &pNewAcl)) ABORT(("Cannot build new access control list.\r\n" "Error code: %d", GetLastError())); if (ERROR_SUCCESS != SetNamedSecurityInfo(path, scheme->type, DACL_SECURITY_INFORMATION, NULL, NULL, pNewAcl, NULL)) ABORT(("Cannot apply new access control list.\r\n" "Error code: %d", GetLastError())); cleanup: LocalFree(pNewAcl); LocalFree(pOldAcl); LocalFree(trusteeName); } static void ChangeOwner(const SchemeType * scheme, char * path, ChangeMode mode) { SECURITY_INFORMATION what; PSID pSidOwner = NULL; PSID pSidGroup = NULL; PSID pSid = NULL; if (popstring(g_temp)) ABORT(("Trustee is missing")); if (NULL == (pSid = ParseSid(g_temp))) ABORT(("Bad trustee (%s)", g_temp)); switch(mode) { case ChangeMode_Owner: what = OWNER_SECURITY_INFORMATION; pSidOwner = pSid; break; case ChangeMode_Group: what = GROUP_SECURITY_INFORMATION; pSidGroup = pSid; break; default: ABORT(("Bug: Unsupported change mode: %d", mode)); } if (ERROR_SUCCESS != SetNamedSecurityInfo(path, scheme->type, what, pSidOwner, pSidGroup, NULL, NULL)) ABORT(("Cannot apply new ownership.\r\n" "Error code: %d", GetLastError())); cleanup: ; } /***************************************************************************** FILESYSTEM BASED ACL HANDLING *****************************************************************************/ static const SchemeType * PopFileArgs(char * pathbuf) { if (!popstring(pathbuf)) { DWORD attr = GetFileAttributes(pathbuf); if (INVALID_FILE_ATTRIBUTES != attr) return FILE_ATTRIBUTE_DIRECTORY & attr ? g_directoryScheme : g_fileScheme; else ABORT(("Invalid filesystem path missing")); } else ABORT(("Filesystem path missing")); cleanup: return NULL; } static void ChangeFileDACL(DWORD mode) { char path[MAX_PATH]; const SchemeType * scheme; if (NULL != (scheme = PopFileArgs(path))) ChangeDACL(scheme, path, mode); } static void ChangeFileOwner(ChangeMode mode) { char path[MAX_PATH]; const SchemeType * scheme; if (NULL != (scheme = PopFileArgs(path))) ChangeOwner(scheme, path, mode); } /***************************************************************************** REGISTRY BASED ACL HANDLING *****************************************************************************/ static char * PopRegKeyArgs() { size_t prefixLen, regkeyLen; int iRootKey; const char * prefix; char * path = NULL; if (popstring(g_temp)) ABORT(("Root key name missing")); iRootKey = ParseEnum(g_rootKeyNames, g_temp); if (!ARRAY_CONTAINS(g_rootKeyPrefixes, iRootKey)) ABORT(("Bad root key name (%s)", g_temp)); if (popstring(g_temp)) ABORT(("Registry key name missing")); prefix = g_rootKeyPrefixes[iRootKey]; prefixLen = strlen(prefix); regkeyLen = strlen(g_temp); path = LocalAlloc(LPTR, prefixLen + regkeyLen + 1); memcpy(path, prefix, prefixLen); memcpy(path + prefixLen, g_temp, regkeyLen); path[prefixLen + regkeyLen] = '\0'; return path; cleanup: return NULL; } static void ChangeRegKeyDACL(DWORD mode) { char * path = PopRegKeyArgs(); if (NULL != path) { ChangeDACL(g_registryScheme, path, mode); LocalFree(path); } } static void ChangeRegKeyOwner(ChangeMode mode) { char * path = PopRegKeyArgs(); if (NULL != path) { ChangeOwner(g_registryScheme, path, mode); LocalFree(path); } } /***************************************************************************** PUBLIC FILE RELATED FUNCTIONS *****************************************************************************/ PUBLIC_FUNCTION(GrantOnFile) ChangeFileDACL(GRANT_ACCESS); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(SetOnFile) ChangeFileDACL(SET_ACCESS); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(DenyOnFile) ChangeFileDACL(DENY_ACCESS); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(RevokeOnFile) ChangeFileDACL(REVOKE_ACCESS); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(SetFileOwner) ChangeFileOwner(ChangeMode_Owner); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(SetFileGroup) ChangeFileOwner(ChangeMode_Group); PUBLIC_FUNCTION_END /***************************************************************************** PUBLIC REGISTRY RELATED FUNCTIONS *****************************************************************************/ PUBLIC_FUNCTION(GrantOnRegKey) ChangeRegKeyDACL(GRANT_ACCESS); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(SetOnRegKey) ChangeRegKeyDACL(SET_ACCESS); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(DenyOnRegKey) ChangeRegKeyDACL(DENY_ACCESS); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(RevokeOnRegKey) ChangeRegKeyDACL(REVOKE_ACCESS); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(SetRegKeyOwner) ChangeRegKeyOwner(ChangeMode_Owner); PUBLIC_FUNCTION_END PUBLIC_FUNCTION(SetRegKeyGroup) ChangeRegKeyOwner(ChangeMode_Group); PUBLIC_FUNCTION_END