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; }
DWORD UmnEvtGetFQDN( char *pszFQDNHostname, size_t len) { DWORD dwError = 0; char pszHostname[1024]; const struct addrinfo hints = { .ai_flags = AI_CANONNAME | AI_ADDRCONFIG, .ai_family = AF_UNSPEC, .ai_socktype = 0, .ai_protocol = 0, .ai_canonname = NULL, .ai_addr = NULL, .ai_next = NULL }; struct addrinfo *paddrs= NULL; struct addrinfo *paddr = NULL; dwError = gethostname(pszHostname, sizeof(pszHostname)); if (dwError) { goto error; } dwError = getaddrinfo(pszHostname, NULL, &hints, &paddrs); if (dwError) { UMN_LOG_ERROR("Failed obtaining fully qualified domain name getaddrinfo: %s\n", gai_strerror(dwError)); goto error; } for(paddr = paddrs; paddr != NULL; paddr = paddrs->ai_next) { if (paddr->ai_canonname) { strncpy(pszFQDNHostname, paddr->ai_canonname, len - 1); if (len > 0 ) { pszFQDNHostname[len - 1] = '\0'; } if (strlen(pszFQDNHostname) < strlen(paddr->ai_canonname)) { dwError = ENAMETOOLONG; } break; } } cleanup: if (paddrs) { freeaddrinfo(paddrs); } return dwError; error: goto cleanup; } DWORD UmnEvtSetEventComputerName( const char *pszEventComputerName) { DWORD dwError = LW_ERROR_SUCCESS; char pszComputerName[1024] = {0}; strncpy(pszComputerName, pszEventComputerName, sizeof(pszComputerName) - 1); pszComputerName[sizeof(pszComputerName) - 1] = '\0'; BOOLEAN isTruncated = (strlen(pszComputerName) < strlen(pszEventComputerName)); dwError = LwMbsToWc16s(pszComputerName, &gEventComputerName); return (dwError ? dwError : ((isTruncated) ? LW_ERROR_ERRNO_ENAMETOOLONG : LW_ERROR_SUCCESS)); }
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; }
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; }