DWORD UmnSrvReadConfig( PUMN_SRV_API_CONFIG *ppConfig ) { DWORD dwError = 0; PUMN_SRV_API_CONFIG pConfig = NULL; dwError = UmnSrvInitConfig(&pConfig); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvReadAllocatedConfig(pConfig); BAIL_ON_UMN_ERROR(dwError); UMN_LOG_VERBOSE("CheckInterval = %d seconds\n", pConfig->CheckInterval); UMN_LOG_VERBOSE("SkipNoLogin = %d\n", pConfig->SkipNoLogin); *ppConfig = pConfig; cleanup: return dwError; error: UmnSrvFreeConfig(pConfig); *ppConfig = NULL; goto cleanup; }
DWORD UmnSrvPollerRefresh( VOID ) { DWORD dwError = 0; if (gbPollerThreadRunning) { gbPollerRefresh = TRUE; dwError = pthread_cond_signal( &gSignalPoller); BAIL_ON_UMN_ERROR(dwError); } else { dwError = ESRCH; BAIL_ON_UMN_ERROR(dwError); } cleanup: return dwError; error: goto cleanup; }
NTSTATUS SvcmStart( PLW_SVCM_INSTANCE pInstance, ULONG ArgCount, PWSTR* ppArgs, ULONG FdCount, int* pFds ) { DWORD dwError = 0; dwError = pthread_rwlock_init(&gUmnConfigLock, NULL); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvInitConfig(&gpAPIConfig); BAIL_ON_UMN_ERROR(dwError); // This function creates threads, so signals must be blocked first dwError = UmnSrvRefreshConfiguration(); BAIL_ON_UMN_ERROR(dwError); dwError = LWNetExtendEnvironmentForKrb5Affinity(TRUE); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvStartPollerThread(); BAIL_ON_UMN_ERROR(dwError); error: return dwError; }
static DWORD UmnSrvWriteADGroupValues( HANDLE hReg, HKEY hGroup, PLSA_SECURITY_OBJECT pGroup ) { DWORD dwError = 0; DWORD dword = 0; PCSTR pString = NULL; dwError = RegSetValueExA( hReg, hGroup, "gr_name", 0, REG_SZ, (PBYTE)pGroup->groupInfo.pszUnixName, strlen(pGroup->groupInfo.pszUnixName) + 1); BAIL_ON_UMN_ERROR(dwError); pString = pGroup->groupInfo.pszPasswd; if (!pString) { pString = "x"; } dwError = RegSetValueExA( hReg, hGroup, "gr_passwd", 0, REG_SZ, (PBYTE)pString, strlen(pString) + 1); BAIL_ON_UMN_ERROR(dwError); dword = pGroup->groupInfo.gid; dwError = RegSetValueExA( hReg, hGroup, "gr_gid", 0, REG_DWORD, (PBYTE)&dword, sizeof(dword)); BAIL_ON_UMN_ERROR(dwError); cleanup: return dwError; error: goto cleanup; }
DWORD UmnSrvStartPollerThread( VOID ) { DWORD dwError = 0; BOOLEAN bDestroyOnError = FALSE; if (gbPollerThreadRunning) { dwError = EEXIST; BAIL_ON_UMN_ERROR(dwError); } bDestroyOnError = TRUE; dwError = pthread_cond_init( &gSignalPoller, NULL); BAIL_ON_UMN_ERROR(dwError); gbSignalPollerCreated = TRUE; dwError = pthread_mutex_init( &gSignalPollerMutex, NULL); BAIL_ON_UMN_ERROR(dwError); gbSignalPollerMutexCreated = TRUE; gbPollerThreadShouldExit = FALSE; dwError = pthread_create( &gPollerThread, NULL, UmnSrvPollerThreadRoutine, NULL); BAIL_ON_UMN_ERROR(dwError); gbPollerThreadRunning = TRUE; cleanup: return dwError; error: if (bDestroyOnError) { UmnSrvStopPollerThread(); } goto cleanup; }
DWORD UmnSrvInitConfig( PUMN_SRV_API_CONFIG *ppConfig ) { PUMN_SRV_API_CONFIG pConfig = NULL; DWORD dwError = 0; dwError = RTL_ALLOCATE( &pConfig, UMN_SRV_API_CONFIG, sizeof(*pConfig)); BAIL_ON_UMN_ERROR(dwError); pConfig->CheckInterval = 60 * 30; pConfig->SkipNoLogin = FALSE; *ppConfig = pConfig; cleanup: return dwError; error: *ppConfig = NULL; UmnSrvFreeConfig(pConfig); goto cleanup; }
DWORD UmnSrvStopPollerThread( VOID ) { DWORD dwError = 0; gbPollerThreadShouldExit = TRUE; if (gbPollerThreadRunning) { dwError = pthread_cond_signal( &gSignalPoller); BAIL_ON_UMN_ERROR(dwError); dwError = pthread_join( gPollerThread, NULL); BAIL_ON_UMN_ERROR(dwError); gbPollerThreadRunning = FALSE; } if (gbSignalPollerCreated) { dwError = pthread_cond_destroy(&gSignalPoller); BAIL_ON_UMN_ERROR(dwError); gbSignalPollerCreated = FALSE; } if (gbSignalPollerMutexCreated) { dwError = pthread_mutex_destroy(&gSignalPollerMutex); BAIL_ON_UMN_ERROR(dwError); } cleanup: return dwError; error: goto cleanup; }
DWORD UmnSrvRefreshConfiguration( ) { DWORD dwError = 0; BOOLEAN bUnlockConfigLock = FALSE; PUMN_SRV_API_CONFIG pAPIConfig = NULL; dwError = UmnSrvReadConfig(&pAPIConfig); BAIL_ON_UMN_ERROR(dwError); pthread_rwlock_wrlock(&gUmnConfigLock); bUnlockConfigLock = TRUE; UmnSrvFreeConfig(gpAPIConfig); gpAPIConfig = pAPIConfig; pAPIConfig = NULL; dwError = UmnSrvPollerRefresh(); if (dwError == ESRCH) { // The thread may not be running yet dwError = 0; } BAIL_ON_UMN_ERROR(dwError); cleanup: UmnSrvFreeConfig(pAPIConfig); if (bUnlockConfigLock) { pthread_rwlock_unlock(&gUmnConfigLock); } return(dwError); error: goto cleanup; }
/** * @brief Populate the timespec & timeval with the current time * * @param pDestTimespec * @param pDestTimeval * * @return 0 for success, or errno error code */ DWORD static UmnSrvNow( OUT struct timespec * const pDestTimespec, OUT struct timeval * const pDestTimeval ) { DWORD dwError = 0; dwError = gettimeofday(pDestTimeval, NULL); BAIL_ON_UMN_ERROR(dwError); UmnSrvTimevalToTimespec( pDestTimespec, pDestTimeval); error: return dwError; }
DWORD UmnSrvReadAllocatedConfig( PUMN_SRV_API_CONFIG pConfig ) { DWORD dwError = 0; LWREG_CONFIG_ITEM ConfigDescription[] = { { "CheckInterval", TRUE, LwRegTypeDword, 0, -1, NULL, &pConfig->CheckInterval, NULL }, { "SkipNoLogin", TRUE, LwRegTypeDword, 0, -1, NULL, &pConfig->SkipNoLogin, NULL }, }; UMN_LOG_INFO("Read user monitor configuration settings"); dwError = LwRegProcessConfig( "Services\\" SERVICE_NAME "\\Parameters", "Policy\\Services\\" SERVICE_NAME "\\Parameters", ConfigDescription, sizeof(ConfigDescription)/sizeof(ConfigDescription[0])); BAIL_ON_UMN_ERROR(dwError); error: return dwError; }
/** * @brief Set pElapsed to TRUE if pTimespec has elapsed. * * @param pTimespec * @param pElapsed set to TRUE if pTimespec is <= 'now', * FALSE otherwise, always FALSE on error * @return 0 for success, or errno error code */ DWORD static UmnSrvTimespecElapsed( IN struct timespec const * const pTimespec, OUT BOOLEAN * const pElapsed ) { DWORD dwError = ERROR_SUCCESS; struct timeval now = {0}; struct timespec nowSpec = {0}; *pElapsed = FALSE; dwError = UmnSrvNow(&nowSpec, &now); BAIL_ON_UMN_ERROR(dwError); *pElapsed = (nowSpec.tv_sec > pTimespec->tv_sec || (nowSpec.tv_sec == pTimespec->tv_sec && nowSpec.tv_nsec >= pTimespec->tv_nsec)) ? TRUE : FALSE; error: return dwError; }
static DWORD UmnSrvWriteUserEvent( PLW_EVENTLOG_CONNECTION pEventlog, long long PreviousRun, PUSER_MONITOR_PASSWD pOld, long long Now, struct passwd *pNew ) { DWORD dwError = 0; // Do not free. The field values are borrowed from other structures. USER_CHANGE change = { { 0 } }; LW_EVENTLOG_RECORD record = { 0 }; char oldTimeBuf[128] = { 0 }; char newTimeBuf[128] = { 0 }; struct tm oldTmBuf = { 0 }; struct tm newTmBuf = { 0 }; time_t temp = 0; PCSTR pOperation = NULL; if (PreviousRun) { temp = PreviousRun; localtime_r(&temp, &oldTmBuf); strftime( oldTimeBuf, sizeof(oldTimeBuf), "%Y/%m/%d %H:%M:%S", &oldTmBuf); } else { strcpy(oldTimeBuf, "unknown"); } temp = Now; localtime_r(&temp, &newTmBuf); strftime( newTimeBuf, sizeof(newTimeBuf), "%Y/%m/%d %H:%M:%S", &newTmBuf); if (pOld) { memcpy(&change.OldValue, pOld, sizeof(change.OldValue)); } if (pNew) { change.NewValue.pw_name = pNew->pw_name; change.NewValue.pw_passwd = pNew->pw_passwd; change.NewValue.pw_uid = pNew->pw_uid; change.NewValue.pw_gid = pNew->pw_gid; change.NewValue.pw_gecos = pNew->pw_gecos; change.NewValue.pw_dir = pNew->pw_dir; change.NewValue.pw_shell = pNew->pw_shell; change.NewValue.LastUpdated = Now; } dwError = LwMbsToWc16s( "Application", &record.pLogname); BAIL_ON_UMN_ERROR(dwError); if (!PreviousRun) { dwError = LwMbsToWc16s( "Success Audit", &record.pEventType); } else { dwError = LwMbsToWc16s( "Information", &record.pEventType); } BAIL_ON_UMN_ERROR(dwError); record.EventDateTime = Now; dwError = LwMbsToWc16s( "User Monitor", &record.pEventSource); BAIL_ON_UMN_ERROR(dwError); if (pOld != NULL && pNew != NULL) { pOperation = "changed"; } else if (pOld != NULL && pNew == NULL) { pOperation = "deleted"; } else if (pOld == NULL && pNew != NULL) { pOperation = "added"; } else { dwError = ERROR_INVALID_PARAMETER; BAIL_ON_UMN_ERROR(dwError); } dwError = LwAllocateWc16sPrintfW( &record.pEventCategory, L"User %hhs", pOperation); BAIL_ON_UMN_ERROR(dwError); if (pNew != NULL) { record.EventSourceId = pNew->pw_uid; dwError = LwMbsToWc16s( pNew->pw_name, &record.pUser); BAIL_ON_UMN_ERROR(dwError); } else { record.EventSourceId = pOld->pw_uid; dwError = LwMbsToWc16s( pOld->pw_name, &record.pUser); BAIL_ON_UMN_ERROR(dwError); } // Leave computer NULL so it is filled in by the eventlog dwError = LwAllocateWc16sPrintfW( &record.pDescription, L"Between %hhs and %hhs, user '%hhs' was %hhs.\n" L"Passwd (from passwd struct)\n" L"\tOld: %hhs\n" L"\tNew: %hhs\n" L"Uid\n" L"\tOld: %d\n" L"\tNew: %d\n" L"Primary group id\n" L"\tOld: %d\n" L"\tNew: %d\n" L"Gecos\n" L"\tOld: %hhs\n" L"\tNew: %hhs\n" L"Home directory\n" L"\tOld: %hhs\n" L"\tNew: %hhs\n" L"Shell\n" L"\tOld: %hhs\n" L"\tNew: %hhs", oldTimeBuf, newTimeBuf, pOld ? pOld->pw_name : pNew->pw_name, pOperation, pOld ? pOld->pw_passwd : "", pNew ? pNew->pw_passwd : "", pOld ? pOld->pw_uid : -1, pNew ? pNew->pw_uid : -1, pOld ? pOld->pw_gid : -1, pNew ? pNew->pw_gid : -1, pOld ? pOld->pw_gecos : "", pNew ? pNew->pw_gecos : "", pOld ? pOld->pw_dir : "", pNew ? pNew->pw_dir : "", pOld ? pOld->pw_shell : "", pNew ? pNew->pw_shell : ""); BAIL_ON_UMN_ERROR(dwError); dwError = EncodeUserChange( &change, &record.DataLen, (PVOID*)&record.pData); BAIL_ON_UMN_ERROR(dwError); dwError = LwEvtWriteRecords( pEventlog, 1, &record); BAIL_ON_UMN_ERROR(dwError); cleanup: LW_SAFE_FREE_MEMORY(record.pLogname); LW_SAFE_FREE_MEMORY(record.pEventType); LW_SAFE_FREE_MEMORY(record.pEventSource); LW_SAFE_FREE_MEMORY(record.pEventCategory); LW_SAFE_FREE_MEMORY(record.pUser); LW_SAFE_FREE_MEMORY(record.pDescription); LW_SAFE_FREE_MEMORY(record.pData); return dwError; error: goto cleanup; }
DWORD UmnSrvFindDeletedUsers( PLW_EVENTLOG_CONNECTION pEventlog, HANDLE hReg, PCSTR pUserKeyName, HKEY hUsers, long long Now ) { DWORD dwError = 0; DWORD subKeyCount = 0; DWORD maxSubKeyLen = 0; DWORD subKeyLen = 0; DWORD i = 0; PSTR pKeyName = NULL; DWORD lastUpdated = 0; DWORD lastUpdatedLen = 0; USER_MONITOR_PASSWD old = { 0 }; dwError = RegQueryInfoKeyA( hReg, hUsers, NULL, NULL, NULL, &subKeyCount, &maxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL); BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateMemory( maxSubKeyLen + 1, (PVOID *)&pKeyName); for (i = 0; i < subKeyCount; i++) { if (gbPollerThreadShouldExit) { dwError = ERROR_CANCELLED; BAIL_ON_UMN_ERROR(dwError); } subKeyLen = maxSubKeyLen; dwError = RegEnumKeyExA( hReg, hUsers, i, pKeyName, &subKeyLen, NULL, NULL, NULL, NULL); BAIL_ON_UMN_ERROR(dwError); pKeyName[subKeyLen] = 0; lastUpdatedLen = sizeof(lastUpdated); dwError = RegGetValueA( hReg, hUsers, pKeyName, "LastUpdated", 0, NULL, (PBYTE)&lastUpdated, &lastUpdatedLen); if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE) { UMN_LOG_WARNING("User %s not completely written. The user monitor service may have previously terminated ungracefully.", LW_SAFE_LOG_STRING(pKeyName)); lastUpdated = 0; dwError = 0; } else { BAIL_ON_UMN_ERROR(dwError); } if (lastUpdated < Now) { UmnSrvFreeUserContents(&old); dwError = UmnSrvReadUser( pUserKeyName, pKeyName, &old); BAIL_ON_UMN_ERROR(dwError); UMN_LOG_INFO("User '%s' deleted", old.pw_name); dwError = RegDeleteKeyA( hReg, hUsers, pKeyName); BAIL_ON_UMN_ERROR(dwError); if (!strcmp(pUserKeyName, "Users")) { dwError = UmnSrvWriteUserEvent( pEventlog, old.LastUpdated, &old, Now, NULL); BAIL_ON_UMN_ERROR(dwError); } else { dwError = UmnSrvWriteADUserEvent( pEventlog, old.LastUpdated, &old, Now, NULL); BAIL_ON_UMN_ERROR(dwError); } // Make sure we don't skip the next key since this one was deleted i--; subKeyCount--; } } cleanup: UmnSrvFreeUserContents(&old); LW_SAFE_FREE_STRING(pKeyName); return dwError; error: goto cleanup; }
static DWORD UmnSrvUpdateUser( PLW_EVENTLOG_CONNECTION pEventlog, HANDLE hReg, HKEY hUsers, long long PreviousRun, long long Now, struct passwd *pUser ) { DWORD dwError = 0; HKEY hKey = NULL; USER_MONITOR_PASSWD old = { 0 }; DWORD dwNow = Now; PSTR pEncodedUser = NULL; dwError = LwURLEncodeString( pUser->pw_name, &pEncodedUser); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenKeyExA( hReg, hUsers, pEncodedUser, 0, KEY_ALL_ACCESS, &hKey); if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE) { UMN_LOG_INFO("Adding user '%s' (uid %d)", pUser->pw_name, pUser->pw_uid); dwError = RegCreateKeyExA( hReg, hUsers, pEncodedUser, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hKey, NULL); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteUserValues( hReg, hKey, pUser); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteUserEvent( pEventlog, PreviousRun, NULL, Now, pUser); BAIL_ON_UMN_ERROR(dwError); } else { BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvReadUser( "Users", pEncodedUser, &old); BAIL_ON_UMN_ERROR(dwError); if (strcmp(pUser->pw_name, old.pw_name) || strcmp(pUser->pw_passwd, old.pw_passwd) || pUser->pw_uid != old.pw_uid || pUser->pw_gid != old.pw_gid || strcmp(pUser->pw_gecos, old.pw_gecos) || strcmp(pUser->pw_dir, old.pw_dir) || strcmp(pUser->pw_shell, old.pw_shell)) { UMN_LOG_INFO("User '%s' (uid %d) changed", pUser->pw_name, pUser->pw_uid); dwError = UmnSrvWriteUserValues( hReg, hKey, pUser); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteUserEvent( pEventlog, PreviousRun, &old, Now, pUser); BAIL_ON_UMN_ERROR(dwError); } } dwError = RegSetValueExA( hReg, hKey, "LastUpdated", 0, REG_DWORD, (PBYTE)&dwNow, sizeof(dwNow)); BAIL_ON_UMN_ERROR(dwError); cleanup: LW_SAFE_FREE_STRING(pEncodedUser); UmnSrvFreeUserContents(&old); if (hKey) { RegCloseKey( hReg, hKey); } return dwError; error: goto cleanup; }
DWORD UmnSrvReadUser( PCSTR pParentKey, PCSTR pName, PUSER_MONITOR_PASSWD pResult ) { DWORD dwError = 0; PSTR pUserPath = NULL; LWREG_CONFIG_ITEM userLayout[] = { { "pw_name", FALSE, LwRegTypeString, 0, -1, NULL, &pResult->pw_name, NULL }, { "pw_passwd", FALSE, LwRegTypeString, 0, -1, NULL, &pResult->pw_passwd, NULL }, { "pw_uid", FALSE, LwRegTypeDword, 0, -1, NULL, &pResult->pw_uid, NULL }, { "pw_gid", FALSE, LwRegTypeDword, 0, -1, NULL, &pResult->pw_gid, NULL }, { "pw_gecos", FALSE, LwRegTypeString, 0, -1, NULL, &pResult->pw_gecos, NULL }, { "pw_dir", FALSE, LwRegTypeString, 0, -1, NULL, &pResult->pw_dir, NULL }, { "pw_shell", FALSE, LwRegTypeString, 0, -1, NULL, &pResult->pw_shell, NULL }, { "pDisplayName", FALSE, LwRegTypeString, 0, -1, NULL, &pResult->pDisplayName, NULL }, { "LastUpdated", FALSE, LwRegTypeDword, 0, -1, NULL, &pResult->LastUpdated, NULL }, }; UMN_LOG_VERBOSE("Reading previous values for user '%s'", pName); dwError = LwAllocateStringPrintf( &pUserPath, "Services\\" SERVICE_NAME "\\Parameters\\%s\\%s", pParentKey, pName); BAIL_ON_UMN_ERROR(dwError); dwError = LwRegProcessConfig( pUserPath, NULL, userLayout, sizeof(userLayout)/sizeof(userLayout[0])); BAIL_ON_UMN_ERROR(dwError); cleanup: LW_SAFE_FREE_STRING(pUserPath); return dwError; error: goto cleanup; }
static DWORD UmnSrvWriteUserValues( HANDLE hReg, HKEY hUser, struct passwd *pUser ) { DWORD dwError = 0; DWORD dword = 0; dwError = RegSetValueExA( hReg, hUser, "pw_name", 0, REG_SZ, (PBYTE) pUser->pw_name, strlen(pUser->pw_name) + 1); BAIL_ON_UMN_ERROR(dwError); dwError = RegSetValueExA( hReg, hUser, "pw_passwd", 0, REG_SZ, (PBYTE) pUser->pw_passwd, strlen(pUser->pw_passwd) + 1); BAIL_ON_UMN_ERROR(dwError); dword = pUser->pw_uid; dwError = RegSetValueExA( hReg, hUser, "pw_uid", 0, REG_DWORD, (PBYTE)&dword, sizeof(dword)); BAIL_ON_UMN_ERROR(dwError); dword = pUser->pw_gid; dwError = RegSetValueExA( hReg, hUser, "pw_gid", 0, REG_DWORD, (PBYTE)&dword, sizeof(dword)); BAIL_ON_UMN_ERROR(dwError); dwError = RegSetValueExA( hReg, hUser, "pw_gecos", 0, REG_SZ, (PBYTE) pUser->pw_gecos, strlen(pUser->pw_gecos) + 1); BAIL_ON_UMN_ERROR(dwError); dwError = RegSetValueExA( hReg, hUser, "pw_dir", 0, REG_SZ, (PBYTE) pUser->pw_dir, strlen(pUser->pw_dir) + 1); BAIL_ON_UMN_ERROR(dwError); dwError = RegSetValueExA( hReg, hUser, "pw_shell", 0, REG_SZ, (PBYTE) pUser->pw_shell, strlen(pUser->pw_shell) + 1); BAIL_ON_UMN_ERROR(dwError); cleanup: return dwError; error: goto cleanup; }
static DWORD UmnSrvUpdateADAccountsByHash( HANDLE hLsass, PLW_EVENTLOG_CONNECTION pEventlog, HANDLE hReg, HKEY hParameters, PLW_HASH_TABLE pUsers, long long PreviousRun, long long Now ) { DWORD dwError = 0; HKEY hUsers = NULL; HKEY hGroups = NULL; LW_HASH_ITERATOR usersIterator = { 0 }; LW_HASH_ENTRY* pEntry = NULL; DWORD groupSidCount = 0; PSTR* ppGroupSids = NULL; DWORD lookupGroupSidCount = 0; DWORD lookupGroupSidCapacity = 0; // Only free the first level of this array, do not free the strings it // points to. PSTR* ppLookupGroupSids = NULL; LSA_QUERY_LIST list = { 0 }; PLSA_SECURITY_OBJECT *ppLookedupGroups = NULL; PLW_HASH_TABLE pGroups = NULL; PLW_HASH_TABLE pNameToUser = NULL; PLW_HASH_TABLE pNameToGroup = NULL; DWORD i = 0; // Do not free PLSA_SECURITY_OBJECT pGroup = NULL; // Do not free PLSA_SECURITY_OBJECT pExisting = NULL; PSTR pNewName = NULL; dwError = LwHashCreate( 100, LwHashStringCompare, LwHashStringHash, UmnSrvHashFreeObjectValue, NULL, &pGroups); BAIL_ON_UMN_ERROR(dwError); dwError = LwHashCreate( 100, LwHashStringCompare, LwHashStringHash, NULL, NULL, &pNameToGroup); BAIL_ON_UMN_ERROR(dwError); dwError = LwHashCreate( pUsers->sCount * 2, LwHashStringCompare, LwHashStringHash, NULL, NULL, &pNameToUser); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenKeyExA( hReg, hParameters, "AD Users", 0, KEY_ALL_ACCESS, &hUsers); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenKeyExA( hReg, hParameters, "AD Groups", 0, KEY_ALL_ACCESS, &hGroups); BAIL_ON_UMN_ERROR(dwError); dwError = LwHashGetIterator( pUsers, &usersIterator); BAIL_ON_UMN_ERROR(dwError); while((pEntry = LwHashNext(&usersIterator)) != NULL) { PLSA_SECURITY_OBJECT pUser = (PLSA_SECURITY_OBJECT)pEntry->pValue; if (gbPollerThreadShouldExit) { dwError = ERROR_CANCELLED; BAIL_ON_UMN_ERROR(dwError); } dwError = LwHashGetValue( pNameToUser, pUser->userInfo.pszUnixName, (PVOID*)&pExisting); if (dwError != ERROR_NOT_FOUND) { BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateStringPrintf( &pNewName, "%s\\%s", pUser->pszNetbiosDomainName, pUser->pszSamAccountName); BAIL_ON_UMN_ERROR(dwError); UMN_LOG_ERROR("Found conflict on user name '%hhs'. Sid %hhs will now be reported as name '%s' instead because its alias conflicts with sid %hhs.", pUser->userInfo.pszUnixName, pUser->pszObjectSid, pNewName, pExisting->pszObjectSid); BAIL_ON_UMN_ERROR(dwError); LW_SAFE_FREE_STRING(pUser->userInfo.pszUnixName); pUser->userInfo.pszUnixName = pNewName; pNewName = NULL; } dwError = LwHashSetValue( pNameToUser, pUser->userInfo.pszUnixName, pUser); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvUpdateADUser( pEventlog, hReg, hUsers, PreviousRun, Now, pUser); if (dwError == ERROR_NO_UNICODE_TRANSLATION) { // Error message already logged dwError = 0; continue; } BAIL_ON_UMN_ERROR(dwError); if (ppGroupSids) { LsaFreeSidList( groupSidCount, ppGroupSids); } dwError = LsaQueryMemberOf( hLsass, NULL, 0, 1, &pUser->pszObjectSid, &groupSidCount, &ppGroupSids); BAIL_ON_UMN_ERROR(dwError); if (groupSidCount > lookupGroupSidCapacity) { LW_SAFE_FREE_MEMORY(ppLookupGroupSids); dwError = LwAllocateMemory( groupSidCount * sizeof(ppLookupGroupSids[0]), (PVOID*)&ppLookupGroupSids); BAIL_ON_UMN_ERROR(dwError); lookupGroupSidCapacity = groupSidCount; } lookupGroupSidCount = 0; for (i = 0; i < groupSidCount; i++) { dwError = LwHashGetValue( pGroups, ppGroupSids[i], (PVOID*)&pGroup); if (dwError == ERROR_NOT_FOUND) { ppLookupGroupSids[lookupGroupSidCount++] = ppGroupSids[i]; } else { BAIL_ON_UMN_ERROR(dwError); UMN_LOG_VERBOSE("Found AD user %s is a member of processed group %s", pUser->userInfo.pszUnixName, pGroup->groupInfo.pszUnixName); if (!pGroup->enabled) { UMN_LOG_VERBOSE("Skipping unenabled group %s", pGroup->groupInfo.pszUnixName); } else { dwError = UmnSrvUpdateADGroupMember( pEventlog, hReg, hGroups, PreviousRun, Now, pGroup, pUser->userInfo.pszUnixName); BAIL_ON_UMN_ERROR(dwError); } } } if (lookupGroupSidCount) { list.ppszStrings = (PCSTR *)ppLookupGroupSids; dwError = LsaFindObjects( hLsass, NULL, 0, LSA_OBJECT_TYPE_GROUP, LSA_QUERY_TYPE_BY_SID, lookupGroupSidCount, list, &ppLookedupGroups); BAIL_ON_UMN_ERROR(dwError); for (i = 0; i < lookupGroupSidCount; i++) { if (gbPollerThreadShouldExit) { dwError = ERROR_CANCELLED; BAIL_ON_UMN_ERROR(dwError); } pGroup = ppLookedupGroups[i]; if (!pGroup) { UMN_LOG_ERROR("Unable to find group sid %s that user %s is a member of", ppLookupGroupSids[i], pUser->userInfo.pszUnixName); continue; } UMN_LOG_VERBOSE("Found AD user %s is a member of unprocessed group %s", pUser->userInfo.pszUnixName, pGroup->groupInfo.pszUnixName); dwError = LwHashGetValue( pNameToGroup, pGroup->groupInfo.pszUnixName, (PVOID*)&pExisting); if (dwError != ERROR_NOT_FOUND) { BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateStringPrintf( &pNewName, "%s\\%s", pGroup->pszNetbiosDomainName, pGroup->pszSamAccountName); BAIL_ON_UMN_ERROR(dwError); UMN_LOG_ERROR("Found conflict on group name '%hhs'. Sid %hhs will now be reported as name '%s' instead because its alias conflicts with sid %hhs.", pGroup->groupInfo.pszUnixName, pGroup->pszObjectSid, pNewName, pExisting->pszObjectSid); BAIL_ON_UMN_ERROR(dwError); LW_SAFE_FREE_STRING(pGroup->groupInfo.pszUnixName); pGroup->groupInfo.pszUnixName = pNewName; pNewName = NULL; } dwError = LwHashSetValue( pNameToGroup, pGroup->groupInfo.pszUnixName, pGroup); BAIL_ON_UMN_ERROR(dwError); if (!pGroup->enabled) { UMN_LOG_VERBOSE("Skipping unenabled group %s", pGroup->groupInfo.pszUnixName); } else { dwError = UmnSrvUpdateADGroup( pEventlog, hReg, hGroups, PreviousRun, Now, pGroup); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvUpdateADGroupMember( pEventlog, hReg, hGroups, PreviousRun, Now, pGroup, pUser->userInfo.pszUnixName); BAIL_ON_UMN_ERROR(dwError); } dwError = LwHashSetValue( pGroups, pGroup->pszObjectSid, pGroup); BAIL_ON_UMN_ERROR(dwError); ppLookedupGroups[i] = NULL; } LsaFreeSecurityObjectList( lookupGroupSidCount, ppLookedupGroups); ppLookedupGroups = NULL; } } dwError = UmnSrvFindDeletedUsers( pEventlog, hReg, "AD Users", hUsers, Now); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvFindDeletedGroups( pEventlog, hReg, "AD Groups", hGroups, Now); BAIL_ON_UMN_ERROR(dwError); cleanup: LW_SAFE_FREE_STRING(pNewName); LW_SAFE_FREE_MEMORY(ppLookupGroupSids); if (ppGroupSids) { LsaFreeSidList( groupSidCount, ppGroupSids); } if (ppLookedupGroups) { LsaFreeSecurityObjectList( lookupGroupSidCount, ppLookedupGroups); } if (hUsers) { RegCloseKey(hReg, hUsers); } if (hGroups) { RegCloseKey(hReg, hGroups); } LwHashSafeFree(&pGroups); LwHashSafeFree(&pNameToUser); LwHashSafeFree(&pNameToGroup); return dwError; error: goto cleanup; }
static DWORD UmnSrvUpdateADUser( PLW_EVENTLOG_CONNECTION pEventlog, HANDLE hReg, HKEY hUsers, long long PreviousRun, long long Now, PLSA_SECURITY_OBJECT pUser ) { DWORD dwError = 0; HKEY hKey = NULL; USER_MONITOR_PASSWD old = { 0 }; DWORD dwNow = Now; PSTR pEncodedUser = NULL; dwError = LwURLEncodeString( pUser->userInfo.pszUnixName, &pEncodedUser); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenKeyExA( hReg, hUsers, pEncodedUser, 0, KEY_ALL_ACCESS, &hKey); if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE) { UMN_LOG_INFO("Adding user '%s' (uid %d)", pUser->userInfo.pszUnixName, pUser->userInfo.uid); dwError = RegCreateKeyExA( hReg, hUsers, pEncodedUser, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hKey, NULL); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteADUserValues( hReg, hKey, pUser); if (dwError == ERROR_NO_UNICODE_TRANSLATION) { UMN_LOG_ERROR("Ignoring user with URL encoding %s because one of their fields has no UCS-2 representation", pEncodedUser); // Delete the key so it does not show up with blank values next // time. dwError = RegCloseKey( hReg, hKey); BAIL_ON_UMN_ERROR(dwError); hKey = NULL; dwError = RegDeleteKeyA( hReg, hUsers, pEncodedUser); BAIL_ON_UMN_ERROR(dwError); dwError = ERROR_NO_UNICODE_TRANSLATION; BAIL_ON_UMN_ERROR(dwError); } BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteADUserEvent( pEventlog, PreviousRun, NULL, Now, pUser); BAIL_ON_UMN_ERROR(dwError); } else { BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvReadUser( "AD Users", pEncodedUser, &old); BAIL_ON_UMN_ERROR(dwError); if (strcmp((pUser->userInfo.pszPasswd ? pUser->userInfo.pszPasswd : "x"), old.pw_passwd) || pUser->userInfo.uid != old.pw_uid || pUser->userInfo.gid != old.pw_gid || !UmnSrvStringsEqual(pUser->userInfo.pszGecos, old.pw_gecos) || !UmnSrvStringsEqual(pUser->userInfo.pszHomedir, old.pw_dir) || !UmnSrvStringsEqual(pUser->userInfo.pszShell, old.pw_shell) || !UmnSrvStringsEqual(pUser->userInfo.pszDisplayName, old.pDisplayName)) { UMN_LOG_INFO("User '%s' (uid %d) changed", pUser->userInfo.pszUnixName, pUser->userInfo.uid); dwError = UmnSrvWriteADUserValues( hReg, hKey, pUser); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteADUserEvent( pEventlog, PreviousRun, &old, Now, pUser); BAIL_ON_UMN_ERROR(dwError); } } dwError = RegSetValueExA( hReg, hKey, "LastUpdated", 0, REG_DWORD, (PBYTE)&dwNow, sizeof(dwNow)); BAIL_ON_UMN_ERROR(dwError); cleanup: LW_SAFE_FREE_STRING(pEncodedUser); UmnSrvFreeUserContents(&old); if (hKey) { RegCloseKey( hReg, hKey); } return dwError; error: goto cleanup; }
static DWORD UmnSrvAddUsersFromMembership( HANDLE hLsass, LW_HASH_TABLE *pUsers, PCSTR pLookup ) { DWORD dwError = 0; LSA_QUERY_LIST list; PLSA_SECURITY_OBJECT *ppObjects = NULL; PLSA_SECURITY_OBJECT *ppMembers = NULL; DWORD memberCount = 0; DWORD i = 0; list.ppszStrings = &pLookup; dwError = LsaFindObjects( hLsass, NULL, 0, LSA_OBJECT_TYPE_UNDEFINED, LSA_QUERY_TYPE_BY_NT4, 1, list, &ppObjects); switch(dwError) { // The string was not a valid NT4 name case LW_ERROR_INVALID_PARAMETER: // The user/group does not exist case LW_ERROR_NO_SUCH_OBJECT: goto cleanup; default: BAIL_ON_UMN_ERROR(dwError); } if (ppObjects[0] && ppObjects[0]->type == LSA_OBJECT_TYPE_USER) { if (ppObjects[0]->enabled && !LwHashExists(pUsers, ppObjects[0]->pszObjectSid)) { dwError = LwHashSetValue( pUsers, ppObjects[0]->pszObjectSid, ppObjects[0]); BAIL_ON_UMN_ERROR(dwError); ppObjects[0] = NULL; } } else if (ppObjects[0] && ppObjects[0]->type == LSA_OBJECT_TYPE_GROUP) { dwError = LsaQueryExpandedGroupMembers( hLsass, NULL, 0, LSA_OBJECT_TYPE_USER, ppObjects[0]->pszObjectSid, &memberCount, &ppMembers); BAIL_ON_UMN_ERROR(dwError); for (i = 0; i < memberCount; i++) { if (ppMembers[i]->enabled && !LwHashExists(pUsers, ppMembers[i]->pszObjectSid)) { UMN_LOG_VERBOSE("Found AD user %s that can login because of group %s", ppMembers[i]->userInfo.pszUnixName, pLookup); dwError = LwHashSetValue( pUsers, ppMembers[i]->pszObjectSid, ppMembers[i]); BAIL_ON_UMN_ERROR(dwError); ppMembers[i] = NULL; } } } cleanup: if (ppObjects) { LsaFreeSecurityObjectList( 1, ppObjects); } if (ppMembers) { LsaFreeSecurityObjectList( memberCount, ppMembers); } return dwError; error: goto cleanup; }
static DWORD UmnSrvWriteADUserValues( HANDLE hReg, HKEY hUser, PLSA_SECURITY_OBJECT pUser ) { DWORD dwError = 0; DWORD dword = 0; PCSTR pString = NULL; dwError = RegSetValueExA( hReg, hUser, "pw_name", 0, REG_SZ, (PBYTE) pUser->userInfo.pszUnixName, strlen(pUser->userInfo.pszUnixName) + 1); BAIL_ON_UMN_ERROR(dwError); pString = pUser->userInfo.pszPasswd; if (!pString) { pString = "x"; } dwError = RegSetValueExA( hReg, hUser, "pw_passwd", 0, REG_SZ, (PBYTE) pString, strlen(pString) + 1); BAIL_ON_UMN_ERROR(dwError); dword = pUser->userInfo.uid; dwError = RegSetValueExA( hReg, hUser, "pw_uid", 0, REG_DWORD, (PBYTE)&dword, sizeof(dword)); BAIL_ON_UMN_ERROR(dwError); dword = pUser->userInfo.gid; dwError = RegSetValueExA( hReg, hUser, "pw_gid", 0, REG_DWORD, (PBYTE)&dword, sizeof(dword)); BAIL_ON_UMN_ERROR(dwError); pString = pUser->userInfo.pszGecos; if (!pString) { pString = ""; } dwError = RegSetValueExA( hReg, hUser, "pw_gecos", 0, REG_SZ, (PBYTE) pString, strlen(pString) + 1); BAIL_ON_UMN_ERROR(dwError); dwError = RegSetValueExA( hReg, hUser, "pw_dir", 0, REG_SZ, (PBYTE) pUser->userInfo.pszHomedir, strlen(pUser->userInfo.pszHomedir) + 1); BAIL_ON_UMN_ERROR(dwError); dwError = RegSetValueExA( hReg, hUser, "pw_shell", 0, REG_SZ, (PBYTE) pUser->userInfo.pszShell, strlen(pUser->userInfo.pszShell) + 1); BAIL_ON_UMN_ERROR(dwError); pString = pUser->userInfo.pszDisplayName; if (!pString) { pString = ""; } dwError = RegSetValueExA( hReg, hUser, "pDisplayName", 0, REG_SZ, (PBYTE) pString, strlen(pString) + 1); BAIL_ON_UMN_ERROR(dwError); cleanup: return dwError; error: goto cleanup; }
DWORD UmnSrvUpdateUsers( HANDLE hLsass, PLW_EVENTLOG_CONNECTION pEventlog, HANDLE hReg, HKEY hParameters, long long PreviousRun, long long Now ) { DWORD uid = 0; DWORD dwError = 0; struct passwd *pUser = NULL; LSA_QUERY_LIST list = { 0 }; PLSA_SECURITY_OBJECT* ppObjects = NULL; HKEY hUsers = NULL; BOOLEAN skipNoLogin = FALSE; dwError = UmnSrvGetSkipNoLogin( &skipNoLogin); BAIL_ON_UMN_ERROR(dwError); list.pdwIds = &uid; dwError = RegOpenKeyExA( hReg, hParameters, "Users", 0, KEY_ALL_ACCESS, &hUsers); BAIL_ON_UMN_ERROR(dwError); while ((pUser = getpwent()) != NULL) { if (skipNoLogin && pUser->pw_shell && (!strcmp(pUser->pw_shell, "/sbin/nologin") || !strcmp(pUser->pw_shell, "/bin/nologin") || !strcmp(pUser->pw_shell, "/usr/sbin/nologin") || !strcmp(pUser->pw_shell, "/usr/bin/false") || !strcmp(pUser->pw_shell, "/bin/false"))) { UMN_LOG_VERBOSE("Skipping enumerated user '%s' (uid %d) because their shell prevents them from logging in.", LW_SAFE_LOG_STRING(pUser->pw_name), uid); continue; } uid = pUser->pw_uid; dwError = LsaFindObjects( hLsass, NULL, 0, LSA_OBJECT_TYPE_USER, LSA_QUERY_TYPE_BY_UNIX_ID, 1, list, &ppObjects); if (dwError == ERROR_SUCCESS && ppObjects && ppObjects[0] && ppObjects[0]->enabled && ppObjects[0]->userInfo.pszUnixName && pUser->pw_name && !strcmp(ppObjects[0]->userInfo.pszUnixName, pUser->pw_name)) { UMN_LOG_VERBOSE("Skipping enumerated user '%s' (uid %d) because they came from lsass", LW_SAFE_LOG_STRING(pUser->pw_name), uid); } else { dwError = UmnSrvUpdateUser( pEventlog, hReg, hUsers, PreviousRun, Now, pUser); BAIL_ON_UMN_ERROR(dwError); } if (ppObjects) { LsaFreeSecurityObjectList(1, ppObjects); ppObjects = NULL; } } dwError = UmnSrvFindDeletedUsers( pEventlog, hReg, "Users", hUsers, Now); BAIL_ON_UMN_ERROR(dwError); cleanup: if (ppObjects) { LsaFreeSecurityObjectList(1, ppObjects); } if (hUsers) { RegCloseKey(hReg, hUsers); } return dwError; error: goto cleanup; }
DWORD UmnSrvUpdateADGroup( PLW_EVENTLOG_CONNECTION pEventlog, HANDLE hReg, HKEY hGroups, long long PreviousRun, long long Now, PLSA_SECURITY_OBJECT pGroup ) { DWORD dwError = 0; HKEY hKey = NULL; HKEY hMembers = NULL; USER_MONITOR_GROUP old = { 0 }; DWORD dwNow = Now; old.gr_gid = -1; PSTR pEncodedGroup = NULL; dwError = LwURLEncodeString( pGroup->groupInfo.pszUnixName, &pEncodedGroup); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenKeyExA( hReg, hGroups, pEncodedGroup, 0, KEY_ALL_ACCESS, &hKey); if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE) { dwError = 0; } else { BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvReadGroup( "AD Groups", pEncodedGroup, &old); BAIL_ON_UMN_ERROR(dwError); } // Check if the key does not exist yet, or it was not fully populated. if (old.LastUpdated == 0) { UMN_LOG_INFO("Adding group '%s' (gid %d)", pGroup->groupInfo.pszUnixName, pGroup->groupInfo.gid); dwError = RegCreateKeyExA( hReg, hGroups, pEncodedGroup, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hKey, NULL); if (dwError == LWREG_ERROR_KEYNAME_EXIST) { // The key exists, but the values were not fully populated on a // previous run because the user monitor crashed or was killed. Use // the existing key and let the values get overwritten. dwError = 0; } BAIL_ON_UMN_ERROR(dwError); dwError = RegCreateKeyExA( hReg, hKey, "Members", 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hMembers, NULL); if (dwError == LWREG_ERROR_KEYNAME_EXIST) { // The key exists, but the values were not fully populated on a // previous run because the user monitor crashed or was killed. Use // the existing key and let the values get overwritten. dwError = 0; } BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteADGroupValues( hReg, hKey, pGroup); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteADGroupEvent( pEventlog, PreviousRun, NULL, Now, pGroup); BAIL_ON_UMN_ERROR(dwError); } else if (strcmp(pGroup->groupInfo.pszUnixName, old.gr_name)) { // The group's name changed. This is too drastic of a change for a // change event. File a deletion and addition event. dwError = UmnSrvWriteADGroupEvent( pEventlog, PreviousRun, &old, Now, NULL); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteADGroupEvent( pEventlog, PreviousRun, NULL, Now, pGroup); BAIL_ON_UMN_ERROR(dwError); } else if (strcmp((pGroup->groupInfo.pszPasswd ? pGroup->groupInfo.pszPasswd : "x"), old.gr_passwd) || pGroup->groupInfo.gid != old.gr_gid) { UMN_LOG_INFO("Group '%s' (gid %d) changed", pGroup->groupInfo.pszUnixName, pGroup->groupInfo.gid); dwError = UmnSrvWriteADGroupValues( hReg, hKey, pGroup); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteADGroupEvent( pEventlog, PreviousRun, &old, Now, pGroup); BAIL_ON_UMN_ERROR(dwError); if (pGroup->groupInfo.gid != old.gr_gid) { // Send out membership deletion events for all members. They // will get readded through normal processing with the new gid dwError = RegOpenKeyExA( hReg, hKey, "Members", 0, KEY_ALL_ACCESS, &hMembers); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvFindDeletedGroupMembers( pEventlog, hReg, "AD Groups", hMembers, Now, TRUE, old.gr_gid, old.gr_name); BAIL_ON_UMN_ERROR(dwError); } } dwError = RegSetValueExA( hReg, hKey, "LastUpdated", 0, REG_DWORD, (PBYTE)&dwNow, sizeof(dwNow)); BAIL_ON_UMN_ERROR(dwError); cleanup: LW_SAFE_FREE_STRING(pEncodedGroup); UmnSrvFreeGroupContents(&old); if (hKey) { RegCloseKey( hReg, hKey); } if (hMembers) { RegCloseKey( hReg, hMembers); } return dwError; error: goto cleanup; }
PVOID UmnSrvPollerThreadRoutine( IN PVOID pUnused ) { DWORD dwError = 0; struct timeval now; struct timespec periodStart, periodUsed, nextWake, pushWait = {0}; DWORD dwPeriodSecs = 0; char regErrMsg[256] = {0}; char fqdn[1024] = {0}; BOOLEAN bMutexLocked = FALSE; UMN_LOG_INFO("User poller thread started"); dwError = pthread_mutex_lock(&gSignalPollerMutex); BAIL_ON_UMN_ERROR(dwError); bMutexLocked = TRUE; dwError = UmnSrvNow(&periodStart, &now); BAIL_ON_UMN_ERROR(dwError); while (!gbPollerThreadShouldExit) { dwError = UmnSrvGetCheckInterval(&dwPeriodSecs); BAIL_ON_UMN_ERROR(dwError); pushWait.tv_sec = dwPeriodSecs; UmnSrvTimespecAdd( &nextWake, &periodStart, &pushWait); UMN_LOG_INFO("User poller sleeping for %f seconds", pushWait.tv_sec + pushWait.tv_nsec / (double)NANOSECS_PER_SECOND); while (!gbPollerThreadShouldExit && !gbPollerRefresh) { BOOLEAN bWaitElapsed = FALSE; dwError = pthread_cond_timedwait( &gSignalPoller, &gSignalPollerMutex, &nextWake); if (dwError == EINTR) { UMN_LOG_DEBUG("User poller cond wait interrupted; continuing."); continue; } if (dwError == ETIMEDOUT) { UMN_LOG_DEBUG("User poller cond wait completed."); dwError = 0; break; } if (dwError != 0) { UMN_LOG_ERROR("Timed wait error: error %s (%d).", ErrnoToName(dwError), dwError); BAIL_ON_UMN_ERROR(dwError); } dwError = UmnSrvTimespecElapsed(&nextWake, &bWaitElapsed); if (dwError == 0 && bWaitElapsed == TRUE) { break; } } gbPollerRefresh = FALSE; if (!gbPollerThreadShouldExit) { dwError = UmnSrvNow(&periodStart, &now); BAIL_ON_UMN_ERROR(dwError); // obtain the fully qualified domain name and use it throughout this iteration UmnEvtFreeEventComputerName(); UmnEvtGetFQDN(fqdn, sizeof(fqdn)); UmnEvtSetEventComputerName(fqdn); dwError = UmnSrvUpdateAccountInfo(now.tv_sec); if (dwError == ERROR_CANCELLED) { UMN_LOG_INFO("User poller cancelled iteration"); dwError = 0; break; } // simply log other errors and attempt to continue if (dwError != LW_ERROR_SUCCESS) { if (LwRegIsRegistrySpecificError(dwError)) { LwRegGetErrorString(dwError, regErrMsg, sizeof(regErrMsg) - 1); UMN_LOG_ERROR("Failed updating account info, registry error = %d %s. Continuing.", dwError, regErrMsg); } else { UMN_LOG_ERROR("Failed updating account info, error = %d symbol = %s %s. Continuing.", dwError, LwWin32ExtErrorToName(dwError), LwWin32ExtErrorToDescription(dwError)); } dwError = 0; } // periodUsed = now - periodStart dwError = UmnSrvNow(&periodUsed, &now); BAIL_ON_UMN_ERROR(dwError); UmnSrvTimespecSubtract( &periodUsed, &periodUsed, &periodStart); UMN_LOG_DEBUG("Account activity update took %f seconds", periodUsed.tv_sec + periodUsed.tv_nsec / (double)NANOSECS_PER_SECOND); } } UMN_LOG_INFO("User poller thread stopped"); cleanup: if (bMutexLocked) { pthread_mutex_unlock(&gSignalPollerMutex); } if (dwError != 0) { UMN_LOG_ERROR( "User monitor polling thread exiting with code %d", dwError); kill(getpid(), SIGTERM); } return NULL; error: goto cleanup; }
static DWORD UmnSrvUpdateAccountInfo( long long Now ) { DWORD dwError = 0; HANDLE hLsass = NULL; HANDLE hReg = NULL; HKEY hParameters = NULL; BOOLEAN bLocalDBOpen = FALSE; // Do not free PSTR pDisableLsassEnum = NULL; DWORD lastUpdated = 0; DWORD lastUpdatedLen = sizeof(lastUpdated); PLW_EVENTLOG_CONNECTION pConn = NULL; dwError = LwEvtOpenEventlog( NULL, &pConn); BAIL_ON_UMN_ERROR(dwError); dwError = LsaOpenServer(&hLsass); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenServer(&hReg); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenKeyExA( hReg, NULL, HKEY_THIS_MACHINE "\\Services\\" SERVICE_NAME "\\Parameters", 0, KEY_ALL_ACCESS, &hParameters); BAIL_ON_UMN_ERROR(dwError); dwError = RegGetValueA( hReg, hParameters, NULL, "LastUpdated", 0, NULL, (PBYTE)&lastUpdated, &lastUpdatedLen); if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE) { lastUpdated = 0; dwError = 0; } BAIL_ON_UMN_ERROR(dwError); /* processing local users/groups so disable AD user/group enumeration */ pDisableLsassEnum = getenv("_DISABLE_LSASS_NSS_ENUMERATION"); if (!pDisableLsassEnum || strcmp(pDisableLsassEnum, "1")) { /* Note, this code must leak memory. * * Putenv uses the memory passed to it, that it is it does not copy the * string. There is no Unix standard to unset an environment variable, * and the string passed to putenv must be accessible as long as the * program is running. A static string cannot be used because the * container could out live this service. There is no opportunity to * free the string before the program ends, because the variable must * be accessible for the duration of the program. */ dwError = LwAllocateString( "_DISABLE_LSASS_NSS_ENUMERATION=1", &pDisableLsassEnum); BAIL_ON_UMN_ERROR(dwError); putenv(pDisableLsassEnum); } setpwent(); setgrent(); bLocalDBOpen = TRUE; dwError = UmnSrvUpdateUsers( hLsass, pConn, hReg, hParameters, lastUpdated, Now); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvUpdateGroups( hLsass, pConn, hReg, hParameters, lastUpdated, Now); BAIL_ON_UMN_ERROR(dwError); endpwent(); endgrent(); bLocalDBOpen = FALSE; dwError = UmnSrvUpdateADAccounts( hLsass, pConn, hReg, hParameters, lastUpdated, Now); BAIL_ON_UMN_ERROR(dwError); lastUpdated = Now; dwError = RegSetValueExA( hReg, hParameters, "LastUpdated", 0, REG_DWORD, (PBYTE)&lastUpdated, sizeof(lastUpdated)); BAIL_ON_UMN_ERROR(dwError); cleanup: if (bLocalDBOpen) { endpwent(); endgrent(); } if (hLsass) { LsaCloseServer(hLsass); } if (hReg) { RegCloseServer(hReg); } if (pConn) { LwEvtCloseEventlog(pConn); } return dwError; error: goto cleanup; }
DWORD UmnSrvWriteADGroupEvent( PLW_EVENTLOG_CONNECTION pEventlog, long long PreviousRun, PUSER_MONITOR_GROUP pOld, long long Now, PLSA_SECURITY_OBJECT pNew ) { DWORD dwError = 0; // Do not free. The field values are borrowed from other structures. GROUP_CHANGE change = { { 0 } }; LW_EVENTLOG_RECORD record = { 0 }; char oldTimeBuf[128] = { 0 }; char newTimeBuf[128] = { 0 }; struct tm oldTmBuf = { 0 }; struct tm newTmBuf = { 0 }; time_t temp = 0; PCSTR pOperation = NULL; if (PreviousRun) { temp = PreviousRun; localtime_r(&temp, &oldTmBuf); strftime( oldTimeBuf, sizeof(oldTimeBuf), "%Y/%m/%d %H:%M:%S", &oldTmBuf); } else { strcpy(oldTimeBuf, "unknown"); } temp = Now; localtime_r(&temp, &newTmBuf); strftime( newTimeBuf, sizeof(newTimeBuf), "%Y/%m/%d %H:%M:%S", &newTmBuf); if (pOld) { memcpy(&change.OldValue, pOld, sizeof(change.OldValue)); } if (pNew) { change.NewValue.gr_name = pNew->groupInfo.pszUnixName; change.NewValue.gr_passwd = pNew->groupInfo.pszPasswd ? pNew->groupInfo.pszPasswd : "x"; change.NewValue.gr_gid = pNew->groupInfo.gid; change.NewValue.LastUpdated = Now; } dwError = LwMbsToWc16s( "Application", &record.pLogname); BAIL_ON_UMN_ERROR(dwError); if (!PreviousRun) { dwError = LwMbsToWc16s( "Success Audit", &record.pEventType); } else { dwError = LwMbsToWc16s( "Information", &record.pEventType); } BAIL_ON_UMN_ERROR(dwError); record.EventDateTime = Now; dwError = LwMbsToWc16s( "User Monitor", &record.pEventSource); BAIL_ON_UMN_ERROR(dwError); if (pOld != NULL && pNew != NULL) { pOperation = "changed"; } else if (pOld != NULL && pNew == NULL) { pOperation = "deleted"; } else if (pOld == NULL && pNew != NULL) { pOperation = "added"; } else { dwError = ERROR_INVALID_PARAMETER; BAIL_ON_UMN_ERROR(dwError); } dwError = LwAllocateWc16sPrintfW( &record.pEventCategory, L"AD Group %hhs", pOperation); BAIL_ON_UMN_ERROR(dwError); if (pNew != NULL) { record.EventSourceId = pNew->groupInfo.gid; dwError = LwMbsToWc16s( pNew->groupInfo.pszUnixName, &record.pUser); BAIL_ON_UMN_ERROR(dwError); } else { record.EventSourceId = pOld->gr_gid; dwError = LwMbsToWc16s( pOld->gr_name, &record.pUser); BAIL_ON_UMN_ERROR(dwError); } // Do not free. This value is borrowed from other structures. record.pComputer = (PWSTR)UmnEvtGetEventComputerName(); dwError = LwAllocateWc16sPrintfW( &record.pDescription, L"Between %hhs and %hhs, group '%hhs' was %hhs.\n" L"Passwd (from group struct)\n" L"\tOld: %hhs\n" L"\tNew: %hhs\n" L"Gid\n" L"\tOld: %d\n" L"\tNew: %d", oldTimeBuf, newTimeBuf, pOld ? pOld->gr_name : pNew->groupInfo.pszUnixName, pOperation, pOld ? pOld->gr_passwd : "", pNew ? (pNew->groupInfo.pszPasswd ? pNew->groupInfo.pszPasswd : "x") : "", pOld ? pOld->gr_gid : -1, pNew ? pNew->groupInfo.gid : -1); BAIL_ON_UMN_ERROR(dwError); dwError = EncodeGroupChange( &change, &record.DataLen, (PVOID*)&record.pData); BAIL_ON_UMN_ERROR(dwError); dwError = LwEvtWriteRecords( pEventlog, 1, &record); BAIL_ON_UMN_ERROR(dwError); cleanup: LW_SAFE_FREE_MEMORY(record.pLogname); LW_SAFE_FREE_MEMORY(record.pEventType); LW_SAFE_FREE_MEMORY(record.pEventSource); LW_SAFE_FREE_MEMORY(record.pEventCategory); LW_SAFE_FREE_MEMORY(record.pUser); LW_SAFE_FREE_MEMORY(record.pDescription); LW_SAFE_FREE_MEMORY(record.pData); return dwError; error: goto cleanup; }
DWORD UmnSrvUpdateADAccounts( HANDLE hLsass, PLW_EVENTLOG_CONNECTION pEventlog, HANDLE hReg, HKEY hParameters, long long PreviousRun, long long Now ) { DWORD dwError = 0; PSTR pMemberList = NULL; PCSTR pIter = NULL; PSTR pMember = NULL; PLW_HASH_TABLE pUsers = NULL; LWREG_CONFIG_ITEM ADConfigDescription[] = { { "RequireMembershipOf", TRUE, LwRegTypeMultiString, 0, MAXDWORD, NULL, &pMemberList, NULL }, }; PLSASTATUS pLsaStatus = NULL; // Do not free PSTR pDomain = NULL; // Do not free PSTR pCell = NULL; PLSA_SECURITY_OBJECT pAllUsers = NULL; DWORD i = 0; dwError = LwHashCreate( 100, LwHashStringCompare, LwHashStringHash, UmnSrvHashFreeObjectValue, NULL, &pUsers); BAIL_ON_UMN_ERROR(dwError); dwError = RegProcessConfig( AD_PROVIDER_REGKEY, AD_PROVIDER_POLICY_REGKEY, ADConfigDescription, sizeof(ADConfigDescription)/sizeof(ADConfigDescription[0])); BAIL_ON_UMN_ERROR(dwError); if (pMemberList && pMemberList[0]) { pIter = pMemberList; while (*pIter != 0) { dwError = LwStrDupOrNull( pIter, &pMember); BAIL_ON_UMN_ERROR(dwError); LwStripWhitespace( pMember, TRUE, TRUE); dwError = UmnSrvAddUsersFromMembership( hLsass, pUsers, pMember); BAIL_ON_UMN_ERROR(dwError); pIter += strlen(pIter) + 1; } } else { dwError = LsaGetStatus2( hLsass, NULL, &pLsaStatus); BAIL_ON_UMN_ERROR(dwError); for (i = 0; i < pLsaStatus->dwCount; i++) { if (pLsaStatus->pAuthProviderStatusList[i].pszDomain) { pDomain = pLsaStatus->pAuthProviderStatusList[i].pszDomain; } if (pLsaStatus->pAuthProviderStatusList[i].pszCell) { pCell = pLsaStatus->pAuthProviderStatusList[i].pszCell; } } if (pDomain || pCell) { dwError = LwAllocateMemory( sizeof(*pAllUsers), (PVOID*)&pAllUsers); BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateString( "S-INVALID", &pAllUsers->pszObjectSid); BAIL_ON_UMN_ERROR(dwError); pAllUsers->enabled = TRUE; pAllUsers->bIsLocal = FALSE; dwError = LwAllocateString( "AllDomains", &pAllUsers->pszNetbiosDomainName); BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateString( "AllUsers", &pAllUsers->pszSamAccountName); BAIL_ON_UMN_ERROR(dwError); pAllUsers->type = LSA_OBJECT_TYPE_USER; dwError = LwAllocateString( "S-INVALID", &pAllUsers->userInfo.pszPrimaryGroupSid); BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateString( "All Users", &pAllUsers->userInfo.pszUnixName); BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateString( "All Users", &pAllUsers->userInfo.pszGecos); BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateString( "", &pAllUsers->userInfo.pszShell); BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateString( "", &pAllUsers->userInfo.pszHomedir); BAIL_ON_UMN_ERROR(dwError); if (pCell) { dwError = LwAllocateStringPrintf( &pAllUsers->userInfo.pszDisplayName, "All Users in cell %s", pCell); BAIL_ON_UMN_ERROR(dwError); } else { dwError = LwAllocateStringPrintf( &pAllUsers->userInfo.pszDisplayName, "All Users accessible from domain %s", pDomain); BAIL_ON_UMN_ERROR(dwError); } dwError = LwHashSetValue( pUsers, pAllUsers->pszObjectSid, pAllUsers); BAIL_ON_UMN_ERROR(dwError); pAllUsers = NULL; } } dwError = UmnSrvUpdateADAccountsByHash( hLsass, pEventlog, hReg, hParameters, pUsers, PreviousRun, Now); BAIL_ON_UMN_ERROR(dwError); cleanup: if (pLsaStatus) { LsaFreeStatus(pLsaStatus); } LW_SAFE_FREE_STRING(pMemberList); LW_SAFE_FREE_STRING(pMember); LwHashSafeFree(&pUsers); if (pAllUsers) { LsaFreeSecurityObject(pAllUsers); } return dwError; error: goto cleanup; }
DWORD UmnSrvUpdateADGroupMember( PLW_EVENTLOG_CONNECTION pEventlog, HANDLE hReg, HKEY hGroups, long long PreviousRun, long long Now, PLSA_SECURITY_OBJECT pGroup, PCSTR pMember ) { DWORD dwError = 0; HKEY hKey = NULL; HKEY hMembers = NULL; DWORD dwNow = Now; PSTR pEncodedMember = NULL; PSTR pKeyName = NULL; PSTR pEncodedGroup = NULL; PSTR pMembersKeyName = NULL; dwError = LwURLEncodeString( pMember, &pEncodedMember); BAIL_ON_UMN_ERROR(dwError); dwError = LwURLEncodeString( pGroup->groupInfo.pszUnixName, &pEncodedGroup); BAIL_ON_UMN_ERROR(dwError); dwError = LwAllocateStringPrintf( &pKeyName, "%s\\Members\\%s", pEncodedGroup, pEncodedMember); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenKeyExA( hReg, hGroups, pKeyName, 0, KEY_ALL_ACCESS, &hKey); if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE) { UMN_LOG_INFO("Adding user member '%s' to group '%s' (gid %d)", pMember, pGroup->groupInfo.pszUnixName, pGroup->groupInfo.gid); dwError = LwAllocateStringPrintf( &pMembersKeyName, "%s\\Members", pEncodedGroup); BAIL_ON_UMN_ERROR(dwError); dwError = RegOpenKeyExA( hReg, hGroups, pMembersKeyName, 0, KEY_ALL_ACCESS, &hMembers); if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE) { // Previous run left registry in inconsistent state dwError = RegCreateKeyExA( hReg, hGroups, pMembersKeyName, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hMembers, NULL); BAIL_ON_UMN_ERROR(dwError); } BAIL_ON_UMN_ERROR(dwError); dwError = RegCreateKeyExA( hReg, hMembers, pEncodedMember, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hKey, NULL); BAIL_ON_UMN_ERROR(dwError); dwError = UmnSrvWriteGroupMemberEvent( pEventlog, Now, "AD Groups", PreviousRun, TRUE, //Add member FALSE, //Not gid change pMember, pGroup->groupInfo.gid, pGroup->groupInfo.pszUnixName); BAIL_ON_UMN_ERROR(dwError); } dwError = RegSetValueExA( hReg, hKey, "LastUpdated", 0, REG_DWORD, (PBYTE)&dwNow, sizeof(dwNow)); BAIL_ON_UMN_ERROR(dwError); cleanup: LW_SAFE_FREE_STRING(pEncodedGroup); LW_SAFE_FREE_STRING(pKeyName); LW_SAFE_FREE_STRING(pMembersKeyName); LW_SAFE_FREE_STRING(pEncodedMember); if (hKey) { RegCloseKey( hReg, hKey); } if (hMembers) { RegCloseKey( hReg, hMembers); } return dwError; error: goto cleanup; }