Implement NtAccessCheck.

diff --git a/dlls/ntdll/sec.c b/dlls/ntdll/sec.c
index f986367..b85c0c8 100644
--- a/dlls/ntdll/sec.c
+++ b/dlls/ntdll/sec.c
@@ -1153,23 +1153,85 @@
 /******************************************************************************
  *  NtAccessCheck		[NTDLL.@]
  *  ZwAccessCheck		[NTDLL.@]
+ *
+ * Checks that a user represented by a token is allowed to access an object
+ * represented by a security descriptor.
+ *
+ * PARAMS
+ *  SecurityDescriptor [I] The security descriptor of the object to check.
+ *  ClientToken        [I] Token of the user accessing the object.
+ *  DesiredAccess      [I] The desired access to the object.
+ *  GenericMapping     [I] Mapping used to transform access rights in the SD to their specific forms.
+ *  PrivilegeSet       [I/O] Privileges used during the access check.
+ *  ReturnLength       [O] Number of bytes stored into PrivilegeSet.
+ *  GrantedAccess      [O] The actual access rights granted.
+ *  AccessStatus       [O] The status of the access check.
+ *
+ * RETURNS
+ *  NTSTATUS code.
+ *
+ * NOTES
+ *  DesiredAccess may be MAXIMUM_ALLOWED, in which case the function determines
+ *  the maximum access rights allowed by the SD and returns them in
+ *  GrantedAccess.
+ *  The SecurityDescriptor must have a valid owner and groups present,
+ *  otherwise the function will fail.
  */
 NTSTATUS WINAPI
 NtAccessCheck(
-	IN PSECURITY_DESCRIPTOR SecurityDescriptor,
-	IN HANDLE ClientToken,
-	IN ACCESS_MASK DesiredAccess,
-	IN PGENERIC_MAPPING GenericMapping,
-	OUT PPRIVILEGE_SET PrivilegeSet,
-	OUT PULONG ReturnLength,
-	OUT PULONG GrantedAccess,
-	OUT NTSTATUS *AccessStatus)
+    PSECURITY_DESCRIPTOR SecurityDescriptor,
+    HANDLE ClientToken,
+    ACCESS_MASK DesiredAccess,
+    PGENERIC_MAPPING GenericMapping,
+    PPRIVILEGE_SET PrivilegeSet,
+    PULONG ReturnLength,
+    PULONG GrantedAccess,
+    NTSTATUS *AccessStatus)
 {
-	FIXME("(%p, %p, %08lx, %p, %p, %p, %p, %p), stub\n",
-          SecurityDescriptor, ClientToken, DesiredAccess, GenericMapping,
-          PrivilegeSet, ReturnLength, GrantedAccess, AccessStatus);
-	*AccessStatus = STATUS_SUCCESS;
-	return STATUS_SUCCESS;
+    NTSTATUS status;
+
+    TRACE("(%p, %p, %08lx, %p, %p, %p, %p, %p), stub\n",
+        SecurityDescriptor, ClientToken, DesiredAccess, GenericMapping,
+        PrivilegeSet, ReturnLength, GrantedAccess, AccessStatus);
+
+    SERVER_START_REQ( access_check )
+    {
+        struct security_descriptor sd;
+        const SECURITY_DESCRIPTOR * RealSD = (const SECURITY_DESCRIPTOR *)SecurityDescriptor;
+
+        req->handle = ClientToken;
+        req->desired_access = DesiredAccess;
+        req->mapping_read = GenericMapping->GenericRead;
+        req->mapping_write = GenericMapping->GenericWrite;
+        req->mapping_execute = GenericMapping->GenericExecute;
+        req->mapping_all = GenericMapping->GenericAll;
+
+        /* marshal security descriptor */
+        sd.control = RealSD->Control;
+        sd.owner_len = RtlLengthSid( RealSD->Owner );
+        sd.group_len = RtlLengthSid( RealSD->Group );
+        sd.sacl_len = (RealSD->Sacl ? RealSD->Sacl->AclSize : 0);
+        sd.dacl_len = (RealSD->Dacl ? RealSD->Dacl->AclSize : 0);
+        wine_server_add_data( req, &sd, sizeof(sd) );
+        wine_server_add_data( req, RealSD->Owner, sd.owner_len );
+        wine_server_add_data( req, RealSD->Group, sd.group_len );
+        wine_server_add_data( req, RealSD->Sacl, sd.sacl_len );
+        wine_server_add_data( req, RealSD->Dacl, sd.dacl_len );
+
+        wine_server_set_reply( req, &PrivilegeSet->Privilege, *ReturnLength - FIELD_OFFSET( PRIVILEGE_SET, Privilege ) );
+
+        status = wine_server_call( req );
+
+        *ReturnLength = FIELD_OFFSET( PRIVILEGE_SET, Privilege ) + reply->privileges_len;
+        PrivilegeSet->PrivilegeCount = reply->privileges_len / sizeof(LUID_AND_ATTRIBUTES);
+
+        if (status == STATUS_SUCCESS)
+            *AccessStatus = reply->access_status;
+        *GrantedAccess = reply->access_granted;
+    }
+    SERVER_END_REQ;
+
+    return status;
 }
 
 /******************************************************************************
diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h
index 707ecd6..ddad000 100644
--- a/include/wine/server_protocol.h
+++ b/include/wine/server_protocol.h
@@ -3331,6 +3331,25 @@
     obj_handle_t  new_handle;
 };
 
+struct access_check_request
+{
+    struct request_header __header;
+    obj_handle_t    handle;
+    unsigned int    desired_access;
+    unsigned int    mapping_read;
+    unsigned int    mapping_write;
+    unsigned int    mapping_execute;
+    unsigned int    mapping_all;
+    /* VARARG(sd,security_descriptor); */
+};
+struct access_check_reply
+{
+    struct reply_header __header;
+    unsigned int    access_granted;
+    unsigned int    access_status;
+    unsigned int    privileges_len;
+    /* VARARG(privileges,LUID_AND_ATTRIBUTES); */
+};
 
 
 struct create_mailslot_request
@@ -3574,6 +3593,7 @@
     REQ_get_token_privileges,
     REQ_check_token_privileges,
     REQ_duplicate_token,
+    REQ_access_check,
     REQ_create_mailslot,
     REQ_open_mailslot,
     REQ_set_mailslot_info,
@@ -3773,6 +3793,7 @@
     struct get_token_privileges_request get_token_privileges_request;
     struct check_token_privileges_request check_token_privileges_request;
     struct duplicate_token_request duplicate_token_request;
+    struct access_check_request access_check_request;
     struct create_mailslot_request create_mailslot_request;
     struct open_mailslot_request open_mailslot_request;
     struct set_mailslot_info_request set_mailslot_info_request;
@@ -3970,11 +3991,12 @@
     struct get_token_privileges_reply get_token_privileges_reply;
     struct check_token_privileges_reply check_token_privileges_reply;
     struct duplicate_token_reply duplicate_token_reply;
+    struct access_check_reply access_check_reply;
     struct create_mailslot_reply create_mailslot_reply;
     struct open_mailslot_reply open_mailslot_reply;
     struct set_mailslot_info_reply set_mailslot_info_reply;
 };
 
-#define SERVER_PROTOCOL_VERSION 175
+#define SERVER_PROTOCOL_VERSION 176
 
 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */
diff --git a/server/protocol.def b/server/protocol.def
index 2dd38c6..bbf2213 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -2344,6 +2344,20 @@
     obj_handle_t  new_handle; /* duplicated handle */
 @END
 
+@REQ(access_check)
+    obj_handle_t    handle; /* handle to the token */
+    unsigned int    desired_access; /* desired access to the object */
+    unsigned int    mapping_read; /* mapping from generic read to specific rights */
+    unsigned int    mapping_write; /* mapping from generic write to specific rights */
+    unsigned int    mapping_execute; /* mapping from generic execute to specific rights */
+    unsigned int    mapping_all; /* mapping from generic all to specific rights */
+    VARARG(sd,security_descriptor); /* security descriptor to check */
+@REPLY
+    unsigned int    access_granted; /* access rights actually granted */
+    unsigned int    access_status; /* was access granted? */
+    unsigned int    privileges_len; /* length needed to store privileges */
+    VARARG(privileges,LUID_AND_ATTRIBUTES); /* privileges used during access check */
+@END
 
 /* Create a mailslot */
 @REQ(create_mailslot)
diff --git a/server/request.h b/server/request.h
index 29b53a0..8172cdb 100644
--- a/server/request.h
+++ b/server/request.h
@@ -292,6 +292,7 @@
 DECL_HANDLER(get_token_privileges);
 DECL_HANDLER(check_token_privileges);
 DECL_HANDLER(duplicate_token);
+DECL_HANDLER(access_check);
 DECL_HANDLER(create_mailslot);
 DECL_HANDLER(open_mailslot);
 DECL_HANDLER(set_mailslot_info);
@@ -490,6 +491,7 @@
     (req_handler)req_get_token_privileges,
     (req_handler)req_check_token_privileges,
     (req_handler)req_duplicate_token,
+    (req_handler)req_access_check,
     (req_handler)req_create_mailslot,
     (req_handler)req_open_mailslot,
     (req_handler)req_set_mailslot_info,
diff --git a/server/token.c b/server/token.c
index 1b94ec4..05c1af7 100644
--- a/server/token.c
+++ b/server/token.c
@@ -61,6 +61,7 @@
 {
     struct object  obj;             /* object header */
     struct list    privileges;      /* privileges available to the token */
+    struct list    groups;          /* groups that the user of this token belongs to (sid_and_attributes) */
     SID           *user;            /* SID of user this token represents */
 };
 
@@ -72,6 +73,19 @@
     unsigned    def      : 1; /* is the privilege enabled by default? */
 };
 
+struct sid_and_attributes
+{
+    struct list entry;
+    int         enabled  : 1; /* is the sid currently enabled? */
+    int         def      : 1; /* is the sid enabled by default? */
+    int         logon    : 1; /* is this a logon sid? */
+    int         mandatory: 1; /* is this sid always enabled? */
+    int         owner    : 1; /* can this sid be an owner of an object? */
+    int         resource : 1; /* is this a domain-local group? */
+    int         deny_only: 1; /* is this a sid that should be use for denying only? */
+    SID         sid;
+};
+
 static void token_dump( struct object *obj, int verbose );
 static void token_destroy( struct object *obj );
 
@@ -92,6 +106,7 @@
 static void token_dump( struct object *obj, int verbose )
 {
     fprintf( stderr, "Security token\n" );
+    /* FIXME: dump token members */
 }
 
 static SID *security_sid_alloc( const SID_IDENTIFIER_AUTHORITY *idauthority, int subauthcount, const unsigned int subauth[] )
@@ -113,6 +128,165 @@
         !memcmp( sid1, sid2, FIELD_OFFSET(SID, SubAuthority[sid1->SubAuthorityCount]) ));
 }
 
+static const ACE_HEADER *ace_next( const ACE_HEADER *ace )
+{
+    return (const ACE_HEADER *)((const char *)ace + ace->AceSize);
+}
+
+static int acl_is_valid( const ACL *acl, size_t size )
+{
+    ULONG i;
+    const ACE_HEADER *ace;
+
+    if (size < sizeof(ACL))
+        return FALSE;
+
+    size = min(size, MAX_ACL_LEN);
+
+    size -= sizeof(ACL);
+
+    ace = (const ACE_HEADER *)(acl + 1);
+    for (i = 0; i < acl->AceCount; i++)
+    {
+        const SID *sid;
+
+        if (size < sizeof(ACE_HEADER))
+            return FALSE;
+        if (size < ace->AceSize)
+            return FALSE;
+        size -= ace->AceSize;
+        switch (ace->AceType)
+        {
+        case ACCESS_DENIED_ACE_TYPE:
+            sid = (const SID *)&((const ACCESS_DENIED_ACE *)ace)->SidStart;
+            break;
+        case ACCESS_ALLOWED_ACE_TYPE:
+            sid = (const SID *)&((const ACCESS_ALLOWED_ACE *)ace)->SidStart;
+            break;
+        case SYSTEM_AUDIT_ACE_TYPE:
+            sid = (const SID *)&((const SYSTEM_AUDIT_ACE *)ace)->SidStart;
+            break;
+        case SYSTEM_ALARM_ACE_TYPE:
+            sid = (const SID *)&((const SYSTEM_ALARM_ACE *)ace)->SidStart;
+            break;
+        default:
+            return FALSE;
+        }
+        if (size < sizeof(SID) ||
+            size < FIELD_OFFSET(SID, SubAuthority[sid->SubAuthorityCount]))
+            return FALSE;
+        ace = ace_next( ace );
+    }
+    return TRUE;
+}
+
+/* gets the discretionary access control list from a security descriptor */
+static inline const ACL *sd_get_dacl( const struct security_descriptor *sd, int *present )
+{
+    *present = (sd->control & SE_DACL_PRESENT ? TRUE : FALSE);
+
+    if (sd->dacl_len)
+        return (const ACL *)((const char *)(sd + 1) +
+            sd->owner_len + sd->group_len + sd->sacl_len);
+    else
+        return NULL;
+}
+
+/* gets the system access control list from a security descriptor */
+static inline const ACL *sd_get_sacl( const struct security_descriptor *sd, int *present )
+{
+    *present = (sd->control & SE_SACL_PRESENT ? TRUE : FALSE);
+
+    if (sd->sacl_len)
+        return (const ACL *)((const char *)(sd + 1) +
+            sd->owner_len + sd->group_len);
+    else
+        return NULL;
+}
+
+/* gets the owner from a security descriptor */
+static inline const SID *sd_get_owner( const struct security_descriptor *sd )
+{
+    if (sd->owner_len)
+        return (const SID *)(sd + 1);
+    else
+        return NULL;
+}
+
+/* gets the primary group from a security descriptor */
+static inline const SID *sd_get_group( const struct security_descriptor *sd )
+{
+    if (sd->group_len)
+        return (const SID *)((const char *)(sd + 1) + sd->owner_len);
+    else
+        return NULL;
+}
+
+/* checks whether all members of a security descriptor fit inside the size
+ * of memory specified */
+static int sd_is_valid( const struct security_descriptor *sd, size_t size )
+{
+    size_t offset = sizeof(struct security_descriptor);
+    const SID *group;
+    const SID *owner;
+    const ACL *sacl;
+    const ACL *dacl;
+    int dummy;
+
+    if (size < offset)
+        return FALSE;
+
+    if ((sd->owner_len >= FIELD_OFFSET(SID, SubAuthority[255])) ||
+        (offset + sd->owner_len > size))
+        return FALSE;
+    owner = sd_get_owner( sd );
+    if (owner)
+    {
+        size_t needed_size = FIELD_OFFSET(SID, SubAuthority[owner->SubAuthorityCount]);
+        if ((sd->owner_len < sizeof(SID)) || (needed_size > sd->owner_len))
+            return FALSE;
+    }
+    offset += sd->owner_len;
+
+    if ((sd->group_len >= FIELD_OFFSET(SID, SubAuthority[255])) ||
+        (offset + sd->group_len > size))
+        return FALSE;
+    group = sd_get_group( sd );
+    if (group)
+    {
+        size_t needed_size = FIELD_OFFSET(SID, SubAuthority[group->SubAuthorityCount]);
+        if ((sd->owner_len < sizeof(SID)) || (needed_size > sd->owner_len))
+            return FALSE;
+    }
+    offset += sd->group_len;
+
+    if ((sd->sacl_len >= MAX_ACL_LEN) || (offset + sd->sacl_len > size))
+        return FALSE;
+    sacl = sd_get_sacl( sd, &dummy );
+    if (sacl && !acl_is_valid( sacl, sd->sacl_len ))
+        return FALSE;
+    offset += sd->sacl_len;
+
+    if ((sd->dacl_len >= MAX_ACL_LEN) || (offset + sd->dacl_len > size))
+        return FALSE;
+    dacl = sd_get_dacl( sd, &dummy );
+    if (dacl && !acl_is_valid( dacl, sd->dacl_len ))
+        return FALSE;
+    offset += sd->dacl_len;
+
+    return TRUE;
+}
+
+/* maps from generic rights to specific rights as given by a mapping */
+static inline void map_generic_mask(unsigned int *mask, const GENERIC_MAPPING *mapping)
+{
+    if (*mask & GENERIC_READ) *mask |= mapping->GenericRead;
+    if (*mask & GENERIC_WRITE) *mask |= mapping->GenericWrite;
+    if (*mask & GENERIC_EXECUTE) *mask |= mapping->GenericExecute;
+    if (*mask & GENERIC_ALL) *mask |= mapping->GenericAll;
+    *mask &= 0x0FFFFFFF;
+}
+
 static inline int is_equal_luid( const LUID *luid1, const LUID *luid2 )
 {
     return (luid1->LowPart == luid2->LowPart && luid1->HighPart == luid2->HighPart);
@@ -159,6 +333,13 @@
         struct privilege *privilege = LIST_ENTRY( cursor, struct privilege, entry );
         privilege_remove( privilege );
     }
+
+    LIST_FOR_EACH_SAFE( cursor, cursor_next, &token->groups )
+    {
+        struct sid_and_attributes *group = LIST_ENTRY( cursor, struct sid_and_attributes, entry );
+        list_remove( &group->entry );
+        free( group );
+    }
 }
 
 static struct token *create_token( const SID *user, const LUID_AND_ATTRIBUTES *privs, unsigned int priv_count )
@@ -168,6 +349,7 @@
     {
         int i;
         list_init( &token->privileges );
+        list_init( &token->groups );
         /* copy user */
         token->user = memdup( user, FIELD_OFFSET(SID, SubAuthority[user->SubAuthorityCount]) );
         if (!token->user)
@@ -175,6 +357,9 @@
             release_object( token );
             return NULL;
         }
+
+        /* FIXME: copy groups */
+
         /* copy privileges */
         for (i = 0; i < priv_count; i++)
         {
@@ -328,6 +513,202 @@
         return (enabled_count > 0);
 }
 
+static int token_sid_present( struct token *token, const SID *sid, int deny )
+{
+    struct sid_and_attributes *group;
+
+    if (security_equal_sid( token->user, sid )) return TRUE;
+
+    LIST_FOR_EACH_ENTRY( group, &token->groups, struct sid_and_attributes, entry )
+    {
+        if (!group->enabled) continue;
+        if (group->deny_only && !deny) continue;
+
+        if (security_equal_sid( &group->sid, sid )) return TRUE;
+    }
+
+    return FALSE;
+}
+
+/* checks access to a security descriptor. sd must have been validated by caller.
+ * it returns STATUS_SUCCESS if access was granted to the object, or an error
+ * status code if not, giving the reason. errors not relating to giving access
+ * to the object are returned in the status parameter. granted_access and
+ * status always have a valid value stored in them on return. */
+static unsigned int token_access_check( struct token *token,
+                                 const struct security_descriptor *sd,
+                                 unsigned int desired_access,
+                                 LUID_AND_ATTRIBUTES *privs,
+                                 unsigned int *priv_count,
+                                 const GENERIC_MAPPING *mapping,
+                                 unsigned int *granted_access,
+                                 unsigned int *status )
+{
+    unsigned int current_access = 0;
+    unsigned int denied_access = 0;
+    ULONG i;
+    const ACL *dacl;
+    int dacl_present;
+    const ACE_HEADER *ace;
+    const SID *owner;
+
+    /* assume success, but no access rights */
+    *status = STATUS_SUCCESS;
+    *granted_access = 0;
+
+    /* fail if desired_access contains generic rights */
+    if (desired_access & (GENERIC_READ|GENERIC_WRITE|GENERIC_EXECUTE|GENERIC_ALL))
+    {
+        *priv_count = 0;
+        *status = STATUS_GENERIC_NOT_MAPPED;
+        return STATUS_ACCESS_DENIED;
+    }
+
+    dacl = sd_get_dacl( sd, &dacl_present );
+    owner = sd_get_owner( sd );
+    if (!owner || !sd_get_group( sd ))
+    {
+        *priv_count = 0;
+        *status = STATUS_INVALID_SECURITY_DESCR;
+        return STATUS_ACCESS_DENIED;
+    }
+
+    /* 1: Grant desired access if the object is unprotected */
+    if (!dacl_present)
+    {
+        *priv_count = 0;
+        *granted_access = desired_access;
+        return STATUS_SUCCESS;
+    }
+    if (!dacl)
+    {
+        *priv_count = 0;
+        return STATUS_ACCESS_DENIED;
+    }
+
+    /* 2: Check if caller wants access to system security part. Note: access
+     * is only granted if specifically asked for */
+    if (desired_access & ACCESS_SYSTEM_SECURITY)
+    {
+        const LUID_AND_ATTRIBUTES security_priv = { SeSecurityPrivilege, 0 };
+        LUID_AND_ATTRIBUTES retpriv = security_priv;
+        if (token_check_privileges( token, TRUE, &security_priv, 1, &retpriv ))
+        {
+            if (priv_count)
+            {
+                /* assumes that there will only be one privilege to return */
+                if (*priv_count >= 1)
+                {
+                    *priv_count = 1;
+                    *privs = retpriv;
+                }
+                else
+                {
+                    *priv_count = 1;
+                    return STATUS_BUFFER_TOO_SMALL;
+                }
+            }
+            current_access |= ACCESS_SYSTEM_SECURITY;
+            if (desired_access == current_access)
+            {
+                *granted_access = current_access;
+                return STATUS_SUCCESS;
+            }
+        }
+        else
+        {
+            *priv_count = 0;
+            return STATUS_PRIVILEGE_NOT_HELD;
+        }
+    }
+    else if (priv_count) *priv_count = 0;
+
+    /* 3: Check whether the token is the owner */
+    /* NOTE: SeTakeOwnershipPrivilege is not checked for here - it is instead
+     * checked when a "set owner" call is made, overriding the access rights
+     * determined here. */
+    if (token_sid_present( token, owner, FALSE ))
+    {
+        current_access |= (READ_CONTROL | WRITE_DAC);
+        if (desired_access == current_access)
+        {
+            *granted_access = current_access;
+            return STATUS_SUCCESS;
+        }
+    }
+
+    /* 4: Grant rights according to the DACL */
+    ace = (const ACE_HEADER *)(dacl + 1);
+    for (i = 0; i < dacl->AceCount; i++)
+    {
+        const ACCESS_ALLOWED_ACE *aa_ace;
+        const ACCESS_DENIED_ACE *ad_ace;
+        const SID *sid;
+        switch (ace->AceType)
+        {
+        case ACCESS_DENIED_ACE_TYPE:
+            ad_ace = (const ACCESS_DENIED_ACE *)ace;
+            sid = (const SID *)&ad_ace->SidStart;
+            if (token_sid_present( token, sid, TRUE ))
+            {
+                unsigned int access = ad_ace->Mask;
+                map_generic_mask(&access, mapping);
+                if (desired_access & MAXIMUM_ALLOWED)
+                    denied_access |= access;
+                else
+                {
+                    denied_access |= (access & ~current_access);
+                    if (desired_access & access)
+                    {
+                        *granted_access = 0;
+                        return STATUS_SUCCESS;
+                    }
+                }
+            }
+            break;
+        case ACCESS_ALLOWED_ACE_TYPE:
+            aa_ace = (const ACCESS_ALLOWED_ACE *)ace;
+            sid = (const SID *)&aa_ace->SidStart;
+            if (token_sid_present( token, sid, FALSE ))
+            {
+                unsigned int access = aa_ace->Mask;
+                map_generic_mask(&access, mapping);
+                if (desired_access & MAXIMUM_ALLOWED)
+                    current_access |= access;
+                else
+                    current_access |= (access & ~denied_access);
+            }
+            break;
+        }
+
+        /* don't bother carrying on checking if we've already got all of
+            * rights we need */
+        if (desired_access == *granted_access)
+            break;
+
+        ace = ace_next( ace );
+    }
+
+    if (desired_access & MAXIMUM_ALLOWED)
+    {
+        *granted_access = current_access & ~denied_access;
+        if (*granted_access)
+            return STATUS_SUCCESS;
+        else
+            return STATUS_ACCESS_DENIED;
+    }
+    else
+    {
+        if ((current_access & desired_access) == desired_access)
+        {
+            *granted_access = current_access & desired_access;
+            return STATUS_SUCCESS;
+        }
+        else
+            return STATUS_ACCESS_DENIED;
+    }
+}
+
 /* open a security token */
 DECL_HANDLER(open_token)
 {
@@ -485,3 +866,55 @@
         release_object( token );
     }
 }
+
+/* checks that a user represented by a token is allowed to access an object
+ * represented by a security descriptor */
+DECL_HANDLER(access_check)
+{
+    size_t sd_size = get_req_data_size();
+    const struct security_descriptor *sd = get_req_data();
+    struct token *token;
+
+    if (!sd_is_valid( sd, sd_size ))
+    {
+        set_error( STATUS_ACCESS_VIOLATION );
+        return;
+    }
+
+    if ((token = (struct token *)get_handle_obj( current->process, req->handle,
+                                                 TOKEN_QUERY,
+                                                 &token_ops )))
+    {
+        GENERIC_MAPPING mapping;
+        unsigned int status;
+        LUID_AND_ATTRIBUTES priv;
+        unsigned int priv_count = 1;
+
+        memset(&priv, 0, sizeof(priv));
+
+        /* FIXME: check token is an impersonation token, if not return
+         * STATUS_NO_IMPERSONATION_TOKEN */
+
+        mapping.GenericRead = req->mapping_read;
+        mapping.GenericWrite = req->mapping_write;
+        mapping.GenericExecute = req->mapping_execute;
+        mapping.GenericAll = req->mapping_all;
+
+        reply->access_status = token_access_check(
+            token, sd, req->desired_access, &priv, &priv_count, &mapping,
+            &reply->access_granted, &status );
+
+        reply->privileges_len = priv_count*sizeof(LUID_AND_ATTRIBUTES);
+
+        if ((priv_count > 0) && (reply->privileges_len <= get_reply_max_size()))
+        {
+            LUID_AND_ATTRIBUTES *privs = set_reply_data_size( priv_count * sizeof(*privs) );
+            memcpy( privs, &priv, sizeof(priv) );
+        }
+
+        if (status != STATUS_SUCCESS)
+            set_error( status );
+
+        release_object( token );
+    }
+}
diff --git a/server/trace.c b/server/trace.c
index 912d4f9..a4bc23d 100644
--- a/server/trace.c
+++ b/server/trace.c
@@ -2872,6 +2872,27 @@
     fprintf( stderr, " new_handle=%p", req->new_handle );
 }
 
+static void dump_access_check_request( const struct access_check_request *req )
+{
+    fprintf( stderr, " handle=%p,", req->handle );
+    fprintf( stderr, " desired_access=%08x,", req->desired_access );
+    fprintf( stderr, " mapping_read=%08x,", req->mapping_read );
+    fprintf( stderr, " mapping_write=%08x,", req->mapping_write );
+    fprintf( stderr, " mapping_execute=%08x,", req->mapping_execute );
+    fprintf( stderr, " mapping_all=%08x,", req->mapping_all );
+    fprintf( stderr, " sd=" );
+    dump_varargs_security_descriptor( cur_size );
+}
+
+static void dump_access_check_reply( const struct access_check_reply *req )
+{
+    fprintf( stderr, " access_granted=%08x,", req->access_granted );
+    fprintf( stderr, " access_status=%08x,", req->access_status );
+    fprintf( stderr, " privileges_len=%08x,", req->privileges_len );
+    fprintf( stderr, " privileges=" );
+    dump_varargs_LUID_AND_ATTRIBUTES( cur_size );
+}
+
 static void dump_create_mailslot_request( const struct create_mailslot_request *req )
 {
     fprintf( stderr, " max_msgsize=%08x,", req->max_msgsize );
@@ -3105,6 +3126,7 @@
     (dump_func)dump_get_token_privileges_request,
     (dump_func)dump_check_token_privileges_request,
     (dump_func)dump_duplicate_token_request,
+    (dump_func)dump_access_check_request,
     (dump_func)dump_create_mailslot_request,
     (dump_func)dump_open_mailslot_request,
     (dump_func)dump_set_mailslot_info_request,
@@ -3300,6 +3322,7 @@
     (dump_func)dump_get_token_privileges_reply,
     (dump_func)dump_check_token_privileges_reply,
     (dump_func)dump_duplicate_token_reply,
+    (dump_func)dump_access_check_reply,
     (dump_func)dump_create_mailslot_reply,
     (dump_func)dump_open_mailslot_reply,
     (dump_func)dump_set_mailslot_info_reply,
@@ -3495,6 +3518,7 @@
     "get_token_privileges",
     "check_token_privileges",
     "duplicate_token",
+    "access_check",
     "create_mailslot",
     "open_mailslot",
     "set_mailslot_info",