- Implement AdjustTokenPrivileges, DuplicateTokenEx and
  GetTokenInformation (for the TokenPrivileges case).
- Return STATUS_NO_TOKEN for OpenThreadToken when there is no token
  set for the thread.

diff --git a/server/object.h b/server/object.h
index e3c2a5d..c42b49c 100644
--- a/server/object.h
+++ b/server/object.h
@@ -153,7 +153,7 @@
 
 /* token functions */
 
-extern struct token *create_token(void);
+extern struct token *create_admin_token(void);
 
 /* atom functions */
 
diff --git a/server/process.c b/server/process.c
index fc69e50..36cf4c1 100644
--- a/server/process.c
+++ b/server/process.c
@@ -278,7 +278,7 @@
     process->exe.namelen     = 0;
     process->exe.filename    = NULL;
     process->group_id        = 0;
-    process->token           = create_token();
+    process->token           = create_admin_token();
     list_init( &process->locks );
     list_init( &process->classes );
 
diff --git a/server/protocol.def b/server/protocol.def
index 94af8ad..1de2607 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -2229,3 +2229,32 @@
 #define SET_GLOBAL_SHELL_WINDOWS   0x01  /* set both main shell and listview windows */
 #define SET_GLOBAL_PROGMAN_WINDOW  0x02
 #define SET_GLOBAL_TASKMAN_WINDOW  0x04
+
+/* Adjust the privileges held by a token */
+@REQ(adjust_token_privileges)
+    obj_handle_t  handle; /* handle to the token */
+    int           disable_all; /* disable all privileges? */
+    int           get_modified_state; /* get modified privileges? */
+    VARARG(privileges,LUID_AND_ATTRIBUTES); /* privileges to enable/disable/remove */
+@REPLY
+    unsigned int  len; /* total length in bytes required to store token privileges */
+    VARARG(privileges,LUID_AND_ATTRIBUTES); /* modified privileges */
+@END
+
+/* Retrieves the set of privileges held by or available to a token */
+@REQ(get_token_privileges)
+    obj_handle_t  handle; /* handle to the token */
+@REPLY
+    unsigned int  len; /* total length in bytes required to store token privileges */
+    VARARG(privileges,LUID_AND_ATTRIBUTES); /* privileges held by or available to a token */
+@END
+
+@REQ(duplicate_token)
+    obj_handle_t  handle; /* handle to the token to duplicate */
+    unsigned int  access; /* access rights to the new token */
+    int           inherit; /* inherit flag */
+    int           primary; /* is the new token to be a primary one? */
+    int           impersonation_level; /* impersonation level of the new token */
+@REPLY
+    obj_handle_t  new_handle; /* duplicated handle */
+@END
diff --git a/server/request.h b/server/request.h
index 2b77919..3902c7b 100644
--- a/server/request.h
+++ b/server/request.h
@@ -283,6 +283,9 @@
 DECL_HANDLER(set_clipboard_info);
 DECL_HANDLER(open_token);
 DECL_HANDLER(set_global_windows);
+DECL_HANDLER(adjust_token_privileges);
+DECL_HANDLER(get_token_privileges);
+DECL_HANDLER(duplicate_token);
 
 #ifdef WANT_REQUEST_HANDLERS
 
@@ -469,6 +472,9 @@
     (req_handler)req_set_clipboard_info,
     (req_handler)req_open_token,
     (req_handler)req_set_global_windows,
+    (req_handler)req_adjust_token_privileges,
+    (req_handler)req_get_token_privileges,
+    (req_handler)req_duplicate_token,
 };
 #endif  /* WANT_REQUEST_HANDLERS */
 
diff --git a/server/token.c b/server/token.c
index 67d42b4..94a2167 100644
--- a/server/token.c
+++ b/server/token.c
@@ -35,9 +35,19 @@
 struct token
 {
     struct object  obj;             /* object header */
+    struct list    privileges;      /* privileges available to the token */
+};
+
+struct privilege
+{
+    struct list entry;
+    LUID        luid;
+    int         enabled  : 1; /* is the privilege currently enabled? */
+    int         def      : 1; /* is the privilege enabled by default? */
 };
 
 static void token_dump( struct object *obj, int verbose );
+static void token_destroy( struct object *obj );
 
 static const struct object_ops token_ops =
 {
@@ -48,7 +58,7 @@
     NULL,                      /* signaled */
     NULL,                      /* satified */
     no_get_fd,                 /* get_fd */
-    no_destroy                 /* destroy */
+    token_destroy              /* destroy */
 };
 
 
@@ -57,12 +67,163 @@
     fprintf( stderr, "Security token\n" );
 }
 
-struct token *create_token( void )
+static inline int is_equal_luid( const LUID *luid1, const LUID *luid2 )
+{
+    return (luid1->LowPart == luid2->LowPart && luid1->HighPart == luid2->HighPart);
+}
+
+static inline void luid_and_attr_from_privilege( LUID_AND_ATTRIBUTES *out, const struct privilege *in)
+{
+    out->Luid = in->luid;
+    out->Attributes =
+        (in->enabled ? SE_PRIVILEGE_ENABLED : 0) |
+        (in->def ? SE_PRIVILEGE_ENABLED_BY_DEFAULT : 0);
+}
+
+static struct privilege *privilege_add( struct token *token, const LUID *luid, int enabled )
+{
+    struct privilege *privilege = mem_alloc( sizeof(*privilege) );
+    if (privilege)
+    {
+        privilege->luid = *luid;
+        privilege->def = privilege->enabled = (enabled != 0);
+        list_add_tail( &token->privileges, &privilege->entry );
+    }
+    return privilege;
+}
+
+static void privilege_remove( struct privilege *privilege )
+{
+    list_remove( &privilege->entry );
+    free( privilege );
+}
+
+static void token_destroy( struct object *obj )
+{
+    struct token* token;
+    struct list *cursor, *cursor_next;
+
+    assert( obj->ops == &token_ops );
+    token = (struct token *)obj;
+
+    LIST_FOR_EACH_SAFE( cursor, cursor_next, &token->privileges )
+    {
+        struct privilege *privilege = LIST_ENTRY( cursor, struct privilege, entry );
+        privilege_remove( privilege );
+    }
+}
+
+static struct token *create_token( const LUID_AND_ATTRIBUTES *privs, unsigned int priv_count )
 {
     struct token *token = alloc_object( &token_ops );
+    if (token)
+    {
+        int i;
+        list_init( &token->privileges );
+        for (i = 0; i < priv_count; i++)
+        {
+            /* note: we don't check uniqueness: the caller must make sure
+             * privs doesn't contain any duplicate luids */
+            if (!privilege_add( token, &privs[i].Luid,
+                                privs[i].Attributes & SE_PRIVILEGE_ENABLED ))
+            {
+                release_object( token );
+                return NULL;
+            }
+        }
+    }
     return token;
 }
 
+struct token *create_admin_token( void )
+{
+    static const LUID_AND_ATTRIBUTES admin_privs[] =
+    {
+        { { 23, 0 }, SE_PRIVILEGE_ENABLED }, /* SeChangeNotifyPrivilege */
+        { {  8, 0 }, 0                    }, /* SeSecurityPrivilege */
+        { { 17, 0 }, 0                    }, /* SeBackupPrivilege */
+        { { 18, 0 }, 0                    }, /* SeRestorePrivilege */
+        { { 12, 0 }, 0                    }, /* SeSystemtimePrivilege */
+        { { 19, 0 }, 0                    }, /* SeShutdownPrivilege */
+        { { 24, 0 }, 0                    }, /* SeRemoteShutdownPrivilege */
+        { {  9, 0 }, 0                    }, /* SeTakeOwnershipPrivilege */
+        { { 20, 0 }, 0                    }, /* SeDebugPrivilege */
+        { { 22, 0 }, 0                    }, /* SeSystemEnvironmentPrivilege */
+        { { 11, 0 }, 0                    }, /* SeSystemProfilePrivilege */
+        { { 13, 0 }, 0                    }, /* SeProfileSingleProcessPrivilege */
+        { { 14, 0 }, 0                    }, /* SeIncreaseBasePriorityPrivilege */
+        { { 10, 0 }, 0                    }, /* SeLoadDriverPrivilege */
+        { { 15, 0 }, 0                    }, /* SeCreatePagefilePrivilege */
+        { {  5, 0 }, 0                    }, /* SeIncreaseQuotaPrivilege */
+        { { 25, 0 }, 0                    }, /* SeUndockPrivilege */
+        { { 28, 0 }, 0                    }, /* SeManageVolumePrivilege */
+        { { 29, 0 }, SE_PRIVILEGE_ENABLED }, /* SeImpersonatePrivilege */
+        { { 30, 0 }, SE_PRIVILEGE_ENABLED }, /* SeCreateGlobalPrivilege */
+    };
+    return create_token( admin_privs, sizeof(admin_privs)/sizeof(admin_privs[0]) );
+}
+
+static struct privilege *token_find_privilege( struct token *token, const LUID *luid, int enabled_only)
+{
+    struct privilege *privilege;
+    LIST_FOR_EACH_ENTRY( privilege, &token->privileges, struct privilege, entry )
+    {
+        if (is_equal_luid( luid, &privilege->luid ))
+        {
+            if (enabled_only && !privilege->enabled)
+                return NULL;
+            return privilege;
+        }
+    }
+    return NULL;
+}
+
+static unsigned int token_adjust_privileges( struct token *token, const LUID_AND_ATTRIBUTES *privs,
+                                             unsigned int count, LUID_AND_ATTRIBUTES *mod_privs,
+                                             unsigned int mod_privs_count)
+{
+    int i;
+    unsigned int modified_count = 0;
+
+    for (i = 0; i < count; i++)
+    {
+        struct privilege *privilege =
+            token_find_privilege( token, &privs[i].Luid, FALSE );
+        if (!privilege)
+        {
+            set_error( STATUS_NOT_ALL_ASSIGNED );
+            continue;
+        }
+
+        if (privs[i].Attributes & SE_PRIVILEGE_REMOVE)
+            privilege_remove( privilege );
+        else
+        {
+            /* save previous state for caller */
+            if (mod_privs_count)
+            {
+                luid_and_attr_from_privilege(mod_privs, privilege);
+                mod_privs++;
+                mod_privs_count--;
+                modified_count++;
+            }
+
+            if (privs[i].Attributes & SE_PRIVILEGE_ENABLED)
+                privilege->enabled = TRUE;
+            else
+                privilege->enabled = FALSE;
+        }
+    }
+    return modified_count;
+}
+
+static void token_disable_privileges( struct token *token )
+{
+    struct privilege *privilege;
+    LIST_FOR_EACH_ENTRY( privilege, &token->privileges, struct privilege, entry )
+        privilege->enabled = FALSE;
+}
+
 /* open a security token */
 DECL_HANDLER(open_token)
 {
@@ -73,6 +234,8 @@
         {
             if (thread->token)
                 reply->token = alloc_handle( current->process, thread->token, TOKEN_ALL_ACCESS, 0);
+            else
+                set_error(STATUS_NO_TOKEN);
             release_object( thread );
         }
     }
@@ -83,7 +246,117 @@
         {
             if (process->token)
                 reply->token = alloc_handle( current->process, process->token, TOKEN_ALL_ACCESS, 0);
+            else
+                set_error(STATUS_NO_TOKEN);
             release_object( process );
         }
     }
 }
+
+/* adjust the privileges held by a token */
+DECL_HANDLER(adjust_token_privileges)
+{
+    struct token *token;
+    unsigned int access = TOKEN_ADJUST_PRIVILEGES;
+
+    if (req->get_modified_state) access |= TOKEN_QUERY;
+
+    if ((token = (struct token *)get_handle_obj( current->process, req->handle,
+                                                 access, &token_ops )))
+    {
+        const LUID_AND_ATTRIBUTES *privs = get_req_data();
+        LUID_AND_ATTRIBUTES *modified_privs = NULL;
+        unsigned int priv_count = get_req_data_size() / sizeof(LUID_AND_ATTRIBUTES);
+        unsigned int modified_priv_count = 0;
+
+        if (req->get_modified_state && !req->disable_all)
+        {
+            int i;
+            /* count modified privs */
+            for (i = 0; i < priv_count; i++)
+            {
+                struct privilege *privilege =
+                    token_find_privilege( token, &privs[i].Luid, FALSE );
+                if (privilege && req->get_modified_state)
+                    modified_priv_count++;
+            }
+            reply->len = modified_priv_count;
+            modified_priv_count = min( modified_priv_count, get_reply_max_size() / sizeof(*modified_privs) );
+            if (modified_priv_count)
+                modified_privs = set_reply_data_size( modified_priv_count * sizeof(*modified_privs) );
+        }
+        reply->len = modified_priv_count * sizeof(*modified_privs);
+
+        if (req->disable_all)
+            token_disable_privileges( token );
+        else
+            modified_priv_count = token_adjust_privileges( token, privs,
+                priv_count, modified_privs, modified_priv_count );
+
+        release_object( token );
+    }
+}
+
+/* retrieves the list of privileges that may be held be the token */
+DECL_HANDLER(get_token_privileges)
+{
+    struct token *token;
+
+    if ((token = (struct token *)get_handle_obj( current->process, req->handle,
+                                                 TOKEN_QUERY,
+                                                 &token_ops )))
+    {
+        int priv_count = 0;
+        LUID_AND_ATTRIBUTES *privs;
+        struct privilege *privilege;
+
+        LIST_FOR_EACH_ENTRY( privilege, &token->privileges, struct privilege, entry )
+            priv_count++;
+
+        reply->len = priv_count * sizeof(*privs);
+        if (reply->len <= get_reply_max_size())
+        {
+            privs = set_reply_data_size( priv_count * sizeof(*privs) );
+            if (privs)
+            {
+                int i = 0;
+                LIST_FOR_EACH_ENTRY( privilege, &token->privileges, struct privilege, entry )
+                {
+                    luid_and_attr_from_privilege( &privs[i], privilege );
+                    i++;
+                }
+            }
+        }
+        else
+            set_error(STATUS_BUFFER_TOO_SMALL);
+
+        release_object( token );
+    }
+}
+
+/* creates a duplicate of the token */
+DECL_HANDLER(duplicate_token)
+{
+    struct token *src_token;
+    if ((src_token = (struct token *)get_handle_obj( current->process, req->handle,
+                                                     TOKEN_DUPLICATE,
+                                                     &token_ops )))
+    {
+        /* FIXME: use req->primary and req->impersonation_level */
+        struct token *token = create_token( NULL, 0 );
+        if (token)
+        {
+            struct privilege *privilege;
+            unsigned int access;
+
+            LIST_FOR_EACH_ENTRY( privilege, &src_token->privileges, struct privilege, entry )
+                privilege_add( token, &privilege->luid, privilege->enabled );
+
+            access = req->access;
+            if (access & MAXIMUM_ALLOWED) access = TOKEN_ALL_ACCESS; /* FIXME: needs general solution */
+            reply->new_handle = alloc_handle( current->process, token, access, req->inherit);
+            release_object( token );
+        }
+        release_object( src_token );
+    }
+}
diff --git a/server/trace.c b/server/trace.c
index b499013..bfd6919 100644
--- a/server/trace.c
+++ b/server/trace.c
@@ -407,6 +407,23 @@
     remove_data( size );
 }
 
+static void dump_varargs_LUID_AND_ATTRIBUTES( size_t size )
+{
+    const LUID_AND_ATTRIBUTES *lat = cur_data;
+    size_t len = size / sizeof(*lat);
+
+    fputc( '{', stderr );
+    while (len > 0)
+    {
+        fprintf( stderr, "{luid=%08lx%08lx,attr=%lx}",
+                 lat->Luid.HighPart, lat->Luid.LowPart, lat->Attributes );
+        lat++;
+        if (--len) fputc( ',', stderr );
+    }
+    fputc( '}', stderr );
+    remove_data( size );
+}
+
 typedef void (*dump_func)( const void *req );
 
 /* Everything below this line is generated automatically by tools/make_requests */
@@ -2623,6 +2640,48 @@
     fprintf( stderr, " old_taskman_window=%p", req->old_taskman_window );
 }
 
+static void dump_adjust_token_privileges_request( const struct adjust_token_privileges_request *req )
+{
+    fprintf( stderr, " handle=%p,", req->handle );
+    fprintf( stderr, " disable_all=%d,", req->disable_all );
+    fprintf( stderr, " get_modified_state=%d,", req->get_modified_state );
+    fprintf( stderr, " privileges=" );
+    dump_varargs_LUID_AND_ATTRIBUTES( cur_size );
+}
+
+static void dump_adjust_token_privileges_reply( const struct adjust_token_privileges_reply *req )
+{
+    fprintf( stderr, " len=%08x,", req->len );
+    fprintf( stderr, " privileges=" );
+    dump_varargs_LUID_AND_ATTRIBUTES( cur_size );
+}
+
+static void dump_get_token_privileges_request( const struct get_token_privileges_request *req )
+{
+    fprintf( stderr, " handle=%p", req->handle );
+}
+
+static void dump_get_token_privileges_reply( const struct get_token_privileges_reply *req )
+{
+    fprintf( stderr, " len=%08x,", req->len );
+    fprintf( stderr, " privileges=" );
+    dump_varargs_LUID_AND_ATTRIBUTES( cur_size );
+}
+
+static void dump_duplicate_token_request( const struct duplicate_token_request *req )
+{
+    fprintf( stderr, " handle=%p,", req->handle );
+    fprintf( stderr, " access=%08x,", req->access );
+    fprintf( stderr, " inherit=%d,", req->inherit );
+    fprintf( stderr, " primary=%d,", req->primary );
+    fprintf( stderr, " impersonation_level=%d", req->impersonation_level );
+}
+
+static void dump_duplicate_token_reply( const struct duplicate_token_reply *req )
+{
+    fprintf( stderr, " new_handle=%p", req->new_handle );
+}
+
 static const dump_func req_dumpers[REQ_NB_REQUESTS] = {
     (dump_func)dump_new_process_request,
     (dump_func)dump_get_new_process_info_request,
@@ -2804,6 +2863,9 @@
     (dump_func)dump_set_clipboard_info_request,
     (dump_func)dump_open_token_request,
     (dump_func)dump_set_global_windows_request,
+    (dump_func)dump_adjust_token_privileges_request,
+    (dump_func)dump_get_token_privileges_request,
+    (dump_func)dump_duplicate_token_request,
 };
 
 static const dump_func reply_dumpers[REQ_NB_REQUESTS] = {
@@ -2987,6 +3049,9 @@
     (dump_func)dump_set_clipboard_info_reply,
     (dump_func)dump_open_token_reply,
     (dump_func)dump_set_global_windows_reply,
+    (dump_func)dump_adjust_token_privileges_reply,
+    (dump_func)dump_get_token_privileges_reply,
+    (dump_func)dump_duplicate_token_reply,
 };
 
 static const char * const req_names[REQ_NB_REQUESTS] = {
@@ -3170,6 +3235,9 @@
     "set_clipboard_info",
     "open_token",
     "set_global_windows",
+    "adjust_token_privileges",
+    "get_token_privileges",
+    "duplicate_token",
 };
 
 /* ### make_requests end ### */