/*++

Copyright (c) 1996 Microsoft Corporation

Module Name:

    security.c

Abstract:

    Helpers for NT security API.

Author:

    Jim Schmidt (jimschm) 05-Feb-1997

Revision History:


    ovidiut     14-Mar-2000 Updated CreateLocalAccount for encrypted password feature
    jimschm     02-Jun-1999 Added SetRegKeySecurity
    jimschm     18-Mar-1998 Updated CreateLocalAccount for random password
                            feature.  Added password change if account
                            already exists.

--*/

#include "pch.h"
#include "migmainp.h"

#include "security.h"
#include "encrypt.h"
#include <ntdsapi.h>
#include <dsgetdc.h>


#ifndef UNICODE
#error UNICODE definition required for account lookup code
#endif

#define UNDOCUMENTED_UI_FLAG        0x0200

//
// NT 5 - net share-specific flag
//


DWORD
ConvertNetRightsToAccessMask (
    IN      DWORD Flags
    )

/*++

Routine Description:

    Routine that converts LAN Man flags into NT security flags.

Arguments:

    Flags   - Access flags used with NetAccess* APIs

Return value:

    A DWORD containing the NT security flags.

--*/

{
    DWORD OutFlags;

    if (Flags == ACCESS_READ) {
        //
        // Read only permissions
        //

        OutFlags = FILE_GENERIC_READ|FILE_GENERIC_EXECUTE;

    } else if (Flags == ACCESS_WRITE) {
        //
        // Change only permission
        //

        OutFlags = FILE_GENERIC_WRITE|DELETE;

    } else if (Flags == (ACCESS_READ|ACCESS_WRITE)) {
        //
        // Full control permissions
        //

        OutFlags = FILE_ALL_ACCESS|UNDOCUMENTED_UI_FLAG;

    } else {
        //
        // Unsupported options... disable the share
        //

        OutFlags = 0;
        DEBUGMSG ((DBG_VERBOSE, "Unsupported permission %u was translated to disable permission", Flags));
    }

    return OutFlags;
}


DWORD
AddAclMember (
    IN OUT  PGROWBUFFER GrowBuf,
    IN      PCTSTR UserOrGroup,
    IN      DWORD Attributes
    )

/*++

Routine Description:

    Appends user/group account, attributes and enable flag to a list
    of members.  This funciton is used to build a list of members which
    is passed to CreateAclFromMemberList to create an ACL.

Arguments:

    GrowBuf     - A GROWBUFFER variable that is zero-initialized

    UserOrGroup - String specifying user name or group

    Attributes  - A list of access rights (a combination of flags
                  from NetAccess* APIs).  Currently the only flags
                  that are used are:

                  0 - Deny all access

                  ACCESS_READ - Read-Only access

                  ACCESS_WRITE - Change-Only access

                  ACCESS_READ|ACCESS_WRITE - Full access

Return value:

    The number of bytes needed to store UserOrGroup, Attributes and Enabled,
    or zero if the function fails.  GrowBuf may be expanded to hold the
    new data.

    GrowBuf must be freed by the caller after the ACL is generated.

--*/

{
    DWORD Size;
    PACLMEMBER AclMemberPtr;
    TCHAR RealName[MAX_USER_NAME];
    DWORD OriginalAttribs;
    BOOL Everyone;
    PCTSTR p;

    p = _tcschr (UserOrGroup, TEXT('\\'));
    if (p) {
        UserOrGroup = _tcsinc (p);
    }

    if (StringMatch (UserOrGroup, TEXT("*"))) {
        _tcssafecpy (RealName, g_EveryoneStr, MAX_USER_NAME);
    } else {
        _tcssafecpy (RealName, UserOrGroup, MAX_USER_NAME);
    }

    Everyone = StringIMatch (RealName, g_EveryoneStr);

    Size = SizeOfString (RealName) + sizeof (ACLMEMBER);
    AclMemberPtr = (PACLMEMBER) GrowBuffer (GrowBuf, Size);

    OriginalAttribs = Attributes;
    if (!Attributes && !Everyone) {
        Attributes = ACCESS_READ|ACCESS_WRITE;
    }

    AclMemberPtr->Attribs = ConvertNetRightsToAccessMask (Attributes);
    AclMemberPtr->Enabled = Everyone || OriginalAttribs != 0;
    AclMemberPtr->Failed  = FALSE;
    StringCopy (AclMemberPtr->UserOrGroup, RealName);

    return Size;
}


PACL
CreateAclFromMemberList (
    PBYTE AclMemberList,
    DWORD MemberCount
    )

/*++

Routine Description:

    CreateAclFromMemberList takes a member list (prepared by AddAclMember)
    and generates an ACL.

Arguments:

    AclMemberList  - A pointer to the buffer maintained by AddAclMember.  This
                     is usually the Buf member of a GROWBUFFER variable.

    MemberCount    - The number of members in AclMemberList (i.e. the number of
                     AddAclMember calls)

Return value:

    A pointer to a MemAlloc'd ACL, or NULL if an error occurred.  Call
    FreeMemberListAcl to free a non-NULL return value.

--*/

{
    PACLMEMBER AclMemberPtr;
    DWORD AllowedAceCount;
    DWORD DeniedAceCount;
    DWORD d;
    PACL Acl = NULL;
    DWORD AclSize;
    BOOL b = FALSE;
    UINT SidSize = 0;

    __try {

        //
        // Create SID array for all members
        //

        AclMemberPtr = (PACLMEMBER) AclMemberList;
        AllowedAceCount = 0;
        DeniedAceCount = 0;

        for (d = 0 ; d < MemberCount ; d++) {
            AclMemberPtr->Sid = GetSidForUser (AclMemberPtr->UserOrGroup);
            if (!AclMemberPtr->Sid) {
                // Mark an error
                AclMemberPtr->Failed = TRUE;
            } else {
                // Found SID, adjust ace count and sid size
                if (AclMemberPtr->Enabled) {
                    AllowedAceCount++;
                } else {
                    DeniedAceCount++;
                }

                SidSize += GetLengthSid (AclMemberPtr->Sid);
            }

            GetNextAclMember (&AclMemberPtr);
        }

        //
        // Calculate size of ACL (an ACL struct plus the ACEs) and allocate it.
        //
        // We subtract a DWORD from the struct size because the actual size of all
        // SidStart members is given by SidSize.
        //

        AclSize = sizeof (ACL) +
                  AllowedAceCount * (sizeof (ACCESS_ALLOWED_ACE) - sizeof (DWORD)) +
                  DeniedAceCount *  (sizeof (ACCESS_DENIED_ACE)  - sizeof (DWORD)) +
                  SidSize;

        Acl = (PACL) MemAlloc (g_hHeap, 0, AclSize);
        if (!Acl) {
            LOG ((LOG_ERROR, "Couldn't allocate an ACL"));
            __leave;
        }


        //
        // Create the ACL
        //

        if (!InitializeAcl (Acl, AclSize, ACL_REVISION)) {
            LOG ((LOG_ERROR, "Couldn't initialize ACL"));
            __leave;
        }

        //
        // Add the access-denied ACLs first
        //

        AclMemberPtr = (PACLMEMBER) AclMemberList;

        for (d = 0 ; d < MemberCount ; d++) {
            if (AclMemberPtr->Failed) {
                continue;
            }

            if (!AclMemberPtr->Enabled) {
                if (!AddAccessDeniedAce (
                        Acl,
                        ACL_REVISION,
                        AclMemberPtr->Attribs,
                        AclMemberPtr->Sid
                        )) {

                    LOG ((
                        LOG_ERROR,
                        "Couldn't add denied ACE for %s",
                        AclMemberPtr->UserOrGroup
                        ));
                }
            }

            GetNextAclMember (&AclMemberPtr);
        }

        //
        // Add the access-enabled ACLs last
        //
        // Reset the SID pointer because CreateAclFromMemberList is the
        // only ones who uses this member
        //

        AclMemberPtr = (PACLMEMBER) AclMemberList;

        for (d = 0 ; d < MemberCount ; d++) {
            if (AclMemberPtr->Failed) {
                continue;
            }

            //
            // Add member to list
            //
            if (AclMemberPtr->Enabled) {
                if (!AddAccessAllowedAce (
                        Acl,
                        ACL_REVISION,
                        AclMemberPtr->Attribs,
                        AclMemberPtr->Sid
                        )) {

                    LOG ((
                        LOG_ERROR,
                        "Couldn't add allowed ACE for %s",
                        AclMemberPtr->UserOrGroup
                        ));
                }
            }

            AclMemberPtr->Sid = NULL;

            GetNextAclMember (&AclMemberPtr);
        }

        b = TRUE;
    }

    __finally {
        if (!b) {
            if (Acl) {
                MemFree (g_hHeap, 0, Acl);
            }
            Acl = NULL;
        }
    }

    return Acl;
}


VOID
FreeMemberListAcl (
    PACL Acl
    )

/*++

Routine Description:

    Routine to free the value returned by CreateAclFromMemberList

Arguments:

    Acl - The return value of CreateAclFromMemberList

Return value:

    none

--*/

{
    if (Acl) {
        MemFree (g_hHeap, 0, (LPVOID) Acl);
    }
}


VOID
GetNextAclMember (
    PACLMEMBER *AclMemberPtrToPtr
    )

/*++

Routine Description:

    GetNextAclMember adjusts an ACLMEMBER pointer to point to the next
    member.  Each member is a variable-length structure, so this funciton
    is required to walk the structure array.

Arguments:

    AclMemberPtrToPtr  - A pointer to a PACLMEMBER variable.

Return value:

    none

--*/

{
    *AclMemberPtrToPtr = (PACLMEMBER) ((PBYTE) (*AclMemberPtrToPtr) +
                                       sizeof (ACLMEMBER) +
                                       SizeOfString ((*AclMemberPtrToPtr)->UserOrGroup)
                                       );
}


LONG
CreateLocalAccount (
    IN     PACCOUNTPROPERTIES Properties,
    IN     PCWSTR User             OPTIONAL
    )

/*++

Routine Description:

    CreateLocalAccount creates an account for a local user

Arguments:

    Properties  - Specifies a set of attributes for a user

    User        - An optional name to override Properties->User

Return value:

    A Win32 error code

--*/

{
    USER_INFO_3 ui;
    PUSER_INFO_3 ExistingInfo;
    DWORD rc;
    LONG ErrParam;
    PCWSTR UnicodeUser;
    PCWSTR UnicodePassword;
    PCWSTR UnicodeFullName;
    PCWSTR UnicodeComment;

    //
    // Create local account
    //

    if (!User) {
        User = Properties->User;
    }

    UnicodeUser         = CreateUnicode (User);
    UnicodePassword     = CreateUnicode (Properties->Password);
    UnicodeComment      = CreateUnicode (Properties->AdminComment);
    UnicodeFullName     = CreateUnicode (Properties->FullName);

    ZeroMemory (&ui, sizeof (ui));
    ui.usri3_name       = (PWSTR) UnicodeUser;
    ui.usri3_password   = (PWSTR) UnicodePassword;
    ui.usri3_comment    = (PWSTR) UnicodeComment;
    ui.usri3_full_name  = (PWSTR) UnicodeFullName;

    ui.usri3_priv         = USER_PRIV_USER; // do not change
    ui.usri3_flags        = UF_SCRIPT|UF_NORMAL_ACCOUNT;
    ui.usri3_acct_expires = TIMEQ_FOREVER;
    ui.usri3_max_storage  = USER_MAXSTORAGE_UNLIMITED;

    ui.usri3_primary_group_id = DOMAIN_GROUP_RID_USERS;
    ui.usri3_max_storage = USER_MAXSTORAGE_UNLIMITED;
    ui.usri3_acct_expires = TIMEQ_FOREVER;

    ui.usri3_password_expired = (INT) g_ConfigOptions.ForcePasswordChange;

    rc = NetUserAdd (NULL, 3, (PBYTE) &ui, &ErrParam);

    if (rc == ERROR_SUCCESS) {
        if (Properties->PasswordAttribs & PASSWORD_ATTR_ENCRYPTED) {
            //
            // change user's password using encrypted password APIs
            //
            rc = SetLocalUserEncryptedPassword (
                    User,
                    Properties->Password,
                    FALSE,
                    Properties->EncryptedPassword,
                    TRUE
                    );
            if (rc != ERROR_SUCCESS) {
                if (rc == ERROR_PASSWORD_RESTRICTION) {
                    LOG ((
                        LOG_WARNING,
                        "Unable to set supplied password on user %s because a password rule has been violated.",
                        User
                        ));
                } else if (rc == ERROR_INVALID_PARAMETER) {
                    LOG ((
                        LOG_WARNING,
                        "Illegal encrypted password supplied for user %s.",
                        User
                        ));
                } else {
                    LOG ((
                        LOG_WARNING,
                        "Unable to set password on user %s, rc=%u",
                        User,
                        rc
                        ));

                    rc = ERROR_INVALID_PARAMETER;
                }
            }
        }
    } else if (rc == NERR_UserExists) {
        //
        // Try to change password if user already exists and this is the intent
        //

        DEBUGMSG ((DBG_WARNING, "User %s already exists", User));

        if ((Properties->PasswordAttribs & PASSWORD_ATTR_DONT_CHANGE_IF_EXIST) == 0) {
            if (Properties->PasswordAttribs & PASSWORD_ATTR_ENCRYPTED) {
                rc = SetLocalUserEncryptedPassword (
                        User,
                        Properties->Password,
                        FALSE,
                        Properties->EncryptedPassword,
                        TRUE
                        );
                if (rc != ERROR_SUCCESS) {
                    if (rc == ERROR_PASSWORD_RESTRICTION) {
                        LOG ((
                            LOG_WARNING,
                            "Unable to set supplied password on user %s because a password rule has been violated.",
                            User
                            ));
                    } else if (rc == ERROR_INVALID_PARAMETER) {
                        LOG ((
                            LOG_WARNING,
                            "Illegal encrypted password supplied for user %s.",
                            User
                            ));
                    } else {
                        LOG ((
                            LOG_WARNING,
                            "Unable to set password on user %s, rc=%u",
                            User,
                            rc
                            ));

                        rc = ERROR_INVALID_PARAMETER;
                    }
                }
            } else {
                rc = NetUserGetInfo (NULL, User, 3, (PBYTE *) &ExistingInfo);
                if (rc == ERROR_SUCCESS) {
                    ExistingInfo->usri3_password  = ui.usri3_password;
                    ExistingInfo->usri3_comment   = ui.usri3_comment;
                    ExistingInfo->usri3_full_name = ui.usri3_full_name;
                    ExistingInfo->usri3_flags     = ui.usri3_flags;
                    ExistingInfo->usri3_password_expired = ui.usri3_password_expired;

                    rc = NetUserSetInfo (NULL, User, 3, (PBYTE) ExistingInfo, &ErrParam);

                    NetApiBufferFree ((PVOID) ExistingInfo);

                    if (rc != ERROR_SUCCESS) {
                        LOG ((LOG_WARNING, "NetUserSetInfo failed for %s. rc=%u.", User, rc));
                        rc = ERROR_INVALID_PARAMETER;
                    }
                } else {
                    LOG ((LOG_WARNING, "NetUserGetInfo failed for %s. rc=%u.", User, rc));
                    rc = ERROR_INVALID_PARAMETER;
                }
            }
        } else {
            rc = ERROR_SUCCESS;
        }
    } else {
        LOG ((LOG_ERROR, "NetUserAdd failed for %s. ErrParam=%i.", User, ErrParam));
    }

    DestroyUnicode (UnicodeUser);
    DestroyUnicode (UnicodePassword);
    DestroyUnicode (UnicodeComment);
    DestroyUnicode (UnicodeFullName);

    return rc;
}


VOID
ClearAdminPassword (
    VOID
    )
{
    ACCOUNTPROPERTIES Properties;

    Properties.Password = L"";
    Properties.AdminComment = L"";
    Properties.User = g_AdministratorStr;
    Properties.FullName = g_AdministratorStr;

    CreateLocalAccount (&Properties, NULL);
}


BOOL
AddSidToLocalGroup (
    PSID Sid,
    PCWSTR Group
    )

/*++

Routine Description:

    Routine that adds the supplied SID to the Administrators group.

Arguments:

    Sid - A valid security id for the user to be added to the
          Administrators group

    Group - Specifies the group name to join the user to

Return value:

    TRUE if the member was added successfully

--*/

{
    LOCALGROUP_MEMBERS_INFO_0 lgrmi0;
    DWORD rc;

    lgrmi0.lgrmi0_sid = Sid;
    rc = NetLocalGroupAddMembers (
               NULL,
               Group,
               0,                    // level 0
               (PBYTE) &lgrmi0,
               1                     // member count
               );

    return rc == ERROR_SUCCESS;
}


NTSTATUS
pGetPrimaryDomainInfo (
    POLICY_PRIMARY_DOMAIN_INFO **PrimaryInfoPtr
    )

/*++

Routine Description:

    Private function that retrieves the primary domain info.

Arguments:

    PrimaryInfoPtr - Pointer to a variable to receive the address
                     of the POLICY_PRIMARY_DOMAIN_INFO structure
                     allocated by the Lsa APIs.  Free memory by
                     calling LsaFreeMemory.

Return value:

    NT status code indicating outcome

--*/


{
    LSA_HANDLE  policyHandle;
    NTSTATUS    status;

    //
    // Open local LSA policy to retrieve domain name
    //

    status = OpenPolicy (
                NULL,                           // local target machine
                POLICY_VIEW_LOCAL_INFORMATION,  // Access type
                &policyHandle                   // resultant policy handle
                );

    if (status == ERROR_SUCCESS) {
        //
        // Query LSA Primary domain info
        //

        status = LsaQueryInformationPolicy (
                     policyHandle,
                     PolicyPrimaryDomainInformation,
                     (PVOID *) PrimaryInfoPtr
                     );

        LsaClose (policyHandle);
    }

    return status;
}


BOOL
GetPrimaryDomainName (
    OUT     PTSTR DomainName
    )
{
    NTSTATUS status;
    POLICY_PRIMARY_DOMAIN_INFO *PrimaryInfo;
    PCTSTR TcharName;

    status = pGetPrimaryDomainInfo (&PrimaryInfo);
    if (status == ERROR_SUCCESS) {
        TcharName = ConvertWtoT (PrimaryInfo->Name.Buffer);
        MYASSERT (TcharName);

        StringCopy (DomainName, TcharName);
        FreeWtoT (TcharName);

        LsaFreeMemory (PrimaryInfo);
    }
    ELSE_DEBUGMSG ((DBG_WARNING, "Can't get primary domain info.  rc=%u", status));

    return status == ERROR_SUCCESS;
}


BOOL
GetPrimaryDomainSid (
    OUT     PBYTE DomainSid,
    IN      UINT MaxBytes
    )
{
    NTSTATUS status;
    POLICY_PRIMARY_DOMAIN_INFO *PrimaryInfo;
    UINT Size;

    status = pGetPrimaryDomainInfo (&PrimaryInfo);
    if (status == ERROR_SUCCESS) {
        Size = GetLengthSid (PrimaryInfo->Sid);
        if (MaxBytes < Size) {
            status = ERROR_INSUFFICIENT_BUFFER;
        } else {
            CopyMemory (DomainSid, PrimaryInfo->Sid, Size);
        }

        LsaFreeMemory (PrimaryInfo);
    }
    ELSE_DEBUGMSG ((DBG_WARNING, "Can't get primary domain SID.  rc=%u", status));

    return status == ERROR_SUCCESS;
}



BOOL
IsMemberOfDomain (
    VOID
    )

/*++

Routine Description:

    Determines if the machine is participating in a domain, or if it is
    only participating in a workgroup.  This determination is done by
    obtaining the primary domain information, looking for the server's
    SID.  If the SID is NULL, the machine is not in a domain.

Arguments:

    none

Return value:

    TRUE if the machine is in a domain, FALSE if its in a workgroup.

--*/

{
    NET_API_STATUS rc;
    PWSTR WorkgroupOrDomain = NULL;
    NETSETUP_JOIN_STATUS Type;

    rc = NetGetJoinInformation (NULL, &WorkgroupOrDomain, &Type);

    DEBUGMSG ((DBG_VERBOSE, "NetGetJoinInformation: name=%s, type=%u", WorkgroupOrDomain, Type));

    if (WorkgroupOrDomain) {
        NetApiBufferFree (WorkgroupOrDomain);
    }

    if (rc != ERROR_SUCCESS) {
        LOG ((LOG_ERROR, "NetGetJoinInformation failed: error %u", rc));
    }

    return rc == ERROR_SUCCESS && Type == NetSetupDomainName;


#if 0
    POLICY_PRIMARY_DOMAIN_INFO *PrimaryInfo;
    BOOL b;
    NTSTATUS rc;

    rc = pGetPrimaryDomainInfo (&PrimaryInfo);
    if (rc == ERROR_SUCCESS) {
        b = PrimaryInfo->Sid != NULL;
    } else {
        b = FALSE;
        SetLastError (rc);
        LOG ((LOG_ERROR, "Can't get domain security info"));
    }

    // Domain name is in PrimaryInfo->Name.Buffer

    LsaFreeMemory (PrimaryInfo) ;

    return b;
#endif

}


LONG
GetAnyDC (
    IN PCWSTR  Domain,
    IN PWSTR   ServerBuf,
    IN BOOL     GetNewServer
    )

/*++

  Routine Description:

    Gets the list of all domain controllers and randomly chooses one.  If
    the listed DC is not online, other listed DCs are queried until an
    alive DC is found.

  Arguments:

    Domain    - The name of the domain to find DCs for
    ServerBuf - A buffer to hold the name of the server

  Return value:

    NT status code indicating outcome.

--*/

{
    DWORD rc;
    PDOMAIN_CONTROLLER_INFO dci;
    DWORD Flags = DS_IS_FLAT_NAME;

    //
    // This API is fast because its WINS based...
    //

    rc = DsGetDcName (
            NULL,           // computer to remote to
            Domain,
            NULL,           // Domain GUID
            NULL,           // Site GUID
            Flags | (GetNewServer ? DS_FORCE_REDISCOVERY : 0),
            &dci
            );

    if (rc == NO_ERROR) {
        StringCopyW (ServerBuf, dci->DomainControllerAddress);
        NetApiBufferFree (dci);

        DEBUGMSG ((DBG_VERBOSE, "Found server %s for the %s domain", ServerBuf, Domain));
        return rc;
    }

    return rc;

}


VOID
InitLsaString (
    OUT     PLSA_UNICODE_STRING LsaString,
    IN      PWSTR String
    )

/*++

Routine Description:

    LSA uses a special Pascal-style string structure.  This
    routine assigns String to a member of LsaString, and computes
    its length and maximum length.

Arguments:

    LsaString - A pointer to the structure to receive a pointer
                to the nul-terminated string, the length in bytes
                (excluding the nul), and the maximum length including
                the nul.

Return value:

    none

--*/


{
    USHORT StringLength;

    if (!String) {
        ZeroMemory (LsaString, sizeof (LSA_UNICODE_STRING));
        return;
    }

    StringLength = ByteCountW (String);
    LsaString->Buffer = String;
    LsaString->Length = StringLength;
    LsaString->MaximumLength = StringLength + sizeof(WCHAR);
}


NTSTATUS
OpenPolicy (
    IN      PWSTR ServerName,
    IN      DWORD DesiredAccess,
    OUT     PLSA_HANDLE policyHandle
    )

/*++

Routine Description:

    A wrapper to simplify LsaOpenPolicy

Arguments:

    ServerName - Supplies the server to open the policy on.  Specify
                 NULL for local machine.

    DesiredAccess - The access flags passed to the LSA API

    policyHandle - Receives the policy handle if successful

Return value:

    NT status code indicating outcome

--*/

{
    LSA_OBJECT_ATTRIBUTES objectAttributes;
    LSA_UNICODE_STRING ServerString;
    PLSA_UNICODE_STRING Server;

    //
    // Always initialize the object attributes to all zeroes
    //
    ZeroMemory (&objectAttributes, sizeof(objectAttributes));

    if (ServerName != NULL) {
        //
        // Make a LSA_UNICODE_STRING out of the PWSTR passed in
        //
        InitLsaString (&ServerString, ServerName);

        Server = &ServerString;
    } else {
        Server = NULL;
    }

    //
    // Attempt to open the policy
    //
    return LsaOpenPolicy (
                Server,
                &objectAttributes,
                DesiredAccess,
                policyHandle
                );
}


BOOL
IsDomainController(
    IN      PWSTR Server,
    OUT     PBOOL DomainControllerFlag
    )

/*++

Routine Description:

    Queries if the machine is a server or workstation via
    the NetServerGetInfo API.

Arguments:

    Server - The machine to query, or NULL for the local machine

    DomainControllerFlag - Receives TRUE if the machine is a
                           domain controller, or FALSE if the
                           machine is a workstation.

Return value:

    TRUE if the API was successful, or FALSE if not.  GetLastError
    gives failure code.

--*/


{
    PSERVER_INFO_101 si101;
    NET_API_STATUS nas;

    nas = NetServerGetInfo(
        Server,
        101,    // info-level
        (PBYTE *) &si101
        );

    if (nas != NO_ERROR) {
        SetLastError (nas);
        return FALSE;
    }

    if ((si101->sv101_type & SV_TYPE_DOMAIN_CTRL) ||
        (si101->sv101_type & SV_TYPE_DOMAIN_BAKCTRL)) {
        //
        // We are dealing with a DC
        //
        *DomainControllerFlag = TRUE;
    } else {
        *DomainControllerFlag = FALSE;
    }

    NetApiBufferFree (si101);

    return TRUE;
}


DWORD
pConvertFlagsToRights (
    DWORD Flags
    )
{
    while (Flags > 0x0f) {
        Flags >>= 4;
    }

    if (Flags & 0x01) {
        return 0;
    }

    if (Flags == 0x02) {
        return ACCESS_READ;
    }

    if (Flags == 0x04) {
        return ACCESS_WRITE;
    }

    if ((Flags & 0x06) == 0x06) {
        return ACCESS_READ|ACCESS_WRITE;
    }

    DEBUGMSG ((DBG_WHOOPS, "Undefined access flags specified: 0x%X", Flags));

    return 0;
}


DWORD
SetRegKeySecurity (
    IN      PCTSTR KeyStr,
    IN      DWORD DaclFlags,            OPTIONAL
    IN      PSID Owner,                 OPTIONAL
    IN      PSID PrimaryGroup,          OPTIONAL
    IN      BOOL Recursive
    )

/*++

Routine Description:

  SetRegKeySecurity updates the security of a registry key, or an entire
  registry node.  The caller can change the DACL, owner or primary group.
  Change of the SACL is intentionally not implemented.

Arguments:

  KeyStr       - Specifies the key to modify the permissions.  If Recursive
                 is set to TRUE, this key will be updated along with all
                 subkeys.
  DaclFlags    - Specifies zero or more SF_* flags, indicating how access to
                 the key should be set.
  Owner        - Specifies the SID of the new owner.
  PrimaryGroup - Specifies the SID of the primary group.
  Recursive    - Specifies TRUE to apply the security to the key and all of
                 its subkeys, or FALSE to update the key only, leaving the
                 subkeys alone.

Return Value:

  A Win32 status code.

--*/

{
    DWORD rc = ERROR_SUCCESS;
    SECURITY_DESCRIPTOR sd;
    GROWBUFFER AclMemberList = GROWBUF_INIT;
    HKEY Key = NULL;
    REGSAM OldSam;
    DWORD AclMembers = 0;
    PACL Acl = NULL;
    SECURITY_INFORMATION WhatToSet = 0;
    REGTREE_ENUM e;
    LONG rc2;

    _try {

        //
        // Open key with full permission
        //

        OldSam = SetRegOpenAccessMode (KEY_ALL_ACCESS);

        Key = OpenRegKeyStr (KeyStr);
        if (!Key) {
            rc = GetLastError();
            __leave;
        }

        //
        // Prepare a security descriptor
        //

        InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

        if (Owner) {
            if (!SetSecurityDescriptorOwner (&sd, Owner, FALSE)) {
                rc = GetLastError();
                __leave;
            }

            WhatToSet |= OWNER_SECURITY_INFORMATION;
        }

        if (PrimaryGroup) {
            if (!SetSecurityDescriptorGroup (&sd, PrimaryGroup, FALSE)) {
                rc = GetLastError();
                __leave;
            }

            WhatToSet |= GROUP_SECURITY_INFORMATION;
        }

        //
        // Add the DACL
        //

        if (DaclFlags & SF_EVERYONE_MASK) {
            AddAclMember (
                &AclMemberList,
                g_EveryoneStr,
                pConvertFlagsToRights (DaclFlags & SF_EVERYONE_MASK)
                );
            AclMembers++;

        }

        if (DaclFlags & SF_ADMINISTRATORS_MASK) {
            AddAclMember (
                &AclMemberList,
                g_AdministratorsGroupStr,
                pConvertFlagsToRights (DaclFlags & SF_ADMINISTRATORS_MASK)
                );
            AclMembers++;
        }

        if (AclMembers) {
            Acl = CreateAclFromMemberList (AclMemberList.Buf, AclMembers);
            if (!Acl) {
                rc = GetLastError();
                __leave;
            }

            WhatToSet |= DACL_SECURITY_INFORMATION;
        }

        //
        // Set the security
        //

        if (Recursive) {
            DEBUGMSG_IF ((
                rc != ERROR_SUCCESS,
                DBG_WARNING,
                "RegSetKeySecurity failed for %s with rc=%u",
                KeyStr,
                rc
                ));

            if (EnumFirstRegKeyInTree (&e, KeyStr)) {
                do {

                    rc2 = RegSetKeySecurity (e.CurrentKey->KeyHandle, WhatToSet, &sd);
                    if (rc2 != ERROR_SUCCESS) {
                        rc = (DWORD) rc2;
                    }

                    DEBUGMSG_IF ((
                        rc2 != ERROR_SUCCESS,
                        DBG_WARNING,
                        "RegSetKeySecurity failed for %s with rc=%u",
                        e.FullKeyName,
                        rc2
                        ));

                } while (EnumNextRegKeyInTree (&e));
            }
        } else {
            rc = (DWORD) RegSetKeySecurity (Key, WhatToSet, &sd);
        }
    }
    __finally {
        FreeGrowBuffer (&AclMemberList);
        if (Key) {
            CloseRegKey (Key);
        }

        SetRegOpenAccessMode (OldSam);

        if (Acl) {
            FreeMemberListAcl (Acl);
        }
    }

    return rc;
}