Example #1
0
NTSTATUS
SvcmStop(
    PLW_SVCM_INSTANCE pInstance
)
{
    UMN_LOG_VERBOSE("Shutting down threads");

    UmnSrvStopPollerThread();

    UmnSrvFreeConfig(gpAPIConfig);
    LW_ASSERT(pthread_rwlock_destroy(&gUmnConfigLock) == 0);

    UMN_LOG_INFO("Usermonitor Service exiting...");

    return 0;
}
Example #2
0
NTSTATUS
SvcmRefresh(
    PLW_SVCM_INSTANCE pInstance
)
{
    DWORD dwError = 0;

    UMN_LOG_VERBOSE("Refreshing configuration");
    dwError = UmnSrvRefreshConfiguration();

    if (dwError) {
        UMN_LOG_WARNING("Failed refreshing configuration: error %d", dwError);
    } else {
        UMN_LOG_INFO("Succeeded refreshing configuration");
    }

    return dwError;
}
Example #3
0
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;
}
Example #4
0
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;
}
Example #5
0
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;
}
Example #6
0
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;
}
Example #7
0
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;
}
Example #8
0
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;
}
Example #9
0
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;
}