delphi3000.com - the free delphi knowledge platform
delphi3000.com - the free delphi knowledge platform
500 Users Online NOW
Have a look at your member-status

connecting people's knowledge


  - Recent ArticlesRSS feed for Recent Articles on delphi3000.com
  - List of All Articles
  - Top Viewed Articles
  - Articles (+Attachem.)
  - Articles Of Interest
  - Categories
  - Top Uploader
  - Search
  - Index

  - My Home
  - Submit an Article
  - My Articles
  - My Personal Data
  - My Bookmarks
  - Activities
  - Login/Logout

  - Sign Up
  - Why Sign Up
  - Newsletter

  - Press
  - Advertise

  - Contact
  - Feedback





Community
Borland
ClubeDelphi
Dr. Bob
UK-BUG
Delphi Meetings
Planeta Delphi



Loremo - the 1.5 liter car coming in 2009




Startblatt.de






Share this article with friendsShare this article with friends
Rate this articleRate this article - to keep the quality of delphi3000.com !
Comment this article or read through previous comments (2)


Impersonating a User on Windows NT is a three step processFormat this article printer-friendly!Bookmark function is only available for registered users!
UNIX-like Substitute User (SU) for Windows NT
Product:
Delphi all versions
Category:
System
Skill Level:
Scoring:
Last Update:
02/22/2002
Search Keys:
delphi delphi3000 article borland vcl code-snippet Windows-NT impersonate user unix
Times Scored:
4
Visits:
5789
Uploader: Stewart Moss
Company: New Heights Software Developme
Reference: N/A
 
Question/Problem/Abstract:
How do I simulate the unix SU command under windows NT. In other words I want to run an app under a different user...
Answer:



This code was not written by me.


The original copyright information is still intact.



(*
SU.DPR for Delphi32 Pascal
by Fred - APIKing - de Jong, Heerlen, Netherlands 1997
    home: frejon@worldonline.nl, office: fjng@cbs.nl

su.cpp
   UNIX-like Substitute User for Windows NT

Usage:
   su [NewDomain\][NewUser] [command-line]
   where:
     NewDomain\ is desired domain logon (\\ is ok also)
     NewUser is the name of the user to be impersonated. Default is Administrator.
     command-line is the command to be executed, with parameters. Default is CMD (Console)

Authors:
   David Wihl (wihl@shore.net)
   Steffen Krause (skrause@informatik.hu-berlin.de)

Revision History:
xx-JUL-1995.
       - Removed restriction on command line (User can now specify anything)
       - Added NewDomain logon on command line
       - Added Unicode support but found bug in LogonUserW
03-JUL-1995. Initial public release

Design:
   Impersonating a User on Windows NT is a three step process:
   1-  Logon the User to create a Security identifier
   2-  Enabling access to the Windows Station so the newly logged on NewUser
     can interact. This is necessary even if the Administrator is logging on.
   3-  Creating a process using the Security identifier

   Different privileges are required for steps (1) and (3). Logging on a User
   (LogonUser()) requires the SeTcbPrivilege. Creating a process as another User
   CreateProcessAsUser()) requires SeAssignPrimary and SeIncreaseQuota privileges.
   To grant these privileges, see the Installation Section.

   These two Security API calls were only stablized in NT 3.51, build 1057. SU will
   not work with earlier versions.

   In NT, there is no direct equivalent of UNIX's rwsr-xr-x file permission.

Restrictions and Limitations:
   - There is no logging of failed or successful usage. A future may incorporate
     writing to the Event Log.

Installation:
   The easiest way to selectively grant the three privileges required to use this
   program is:

   1-  Start the User Manager (MUSRMGR)
   2-  Create a new group (e.g. "SU Users")
   3-  Add the three privileges to the group (via Policies\User Rights):
       "Act as part of the operating system"  - SeTcbPrivilege
       "Increase quotas"                      - SeIncreaseQuota
       "Replace a process level token"        - SeAssignPrimaryToken

     NOTE: The three privileges will only be visible if you check
     "Show Advanced User Rights" in the dialog box.
   4-  Add the desired users to the new group (via User\Properties\Group)

   This program was compiled under Visual C++ 2.1 with the June '95 SDK

For more information about Porting from UNIX to NT check the FAQ:
http://www.shore.net/~wihl/unix2nt.html

*)

PROGRAM su;
{$APPTYPE CONSOLE}

uses
  
Windows,
  
SysUtils { already has SysErrorMessage function };

//{$R VersInfo.RES}

//
// CUSTOMIZATION OPTIONS - put 'em here
const
DEFAULT_USER: string = 'Administrator';  // if we don't specify a username, who are we?
DEFAULT_CMD: string = 'cmd';        // if we don't specify a command, what do we do?
{$DEFINE VERBOSE}            // quiet ?la UNIX, or chatty?
//
// END CUSTOMIZATION OPTIONS
//
const
SECURITY_DESCRIPTOR_REVISION = 1; // from winnt.h, missing in windows.pas
////////////////////////////////////////////////////////////////////////
//                                                                    //
//               NT Defined Privileges                                //
//                                                                    //
////////////////////////////////////////////////////////////////////////
SE_CREATE_TOKEN_NAME              = 'SeCreateTokenPrivilege';
SE_ASSIGNPRIMARYTOKEN_NAME        = 'SeAssignPrimaryTokenPrivilege';
SE_LOCK_MEMORY_NAME               = 'SeLockMemoryPrivilege';
SE_INCREASE_QUOTA_NAME            = 'SeIncreaseQuotaPrivilege';
SE_UNSOLICITED_INPUT_NAME         = 'SeUnsolicitedInputPrivilege';
SE_MACHINE_ACCOUNT_NAME           = 'SeMachineAccountPrivilege';
SE_TCB_NAME                       = 'SeTcbPrivilege';
SE_SECURITY_NAME                  = 'SeSecurityPrivilege';
SE_TAKE_OWNERSHIP_NAME            = 'SeTakeOwnershipPrivilege';
SE_LOAD_DRIVER_NAME               = 'SeLoadDriverPrivilege';
SE_system_PROFILE_NAME            = 'SesystemProfilePrivilege';
SE_systemTIME_NAME                = 'SesystemtimePrivilege';
SE_PROF_SINGLE_PROCESS_NAME       = 'SeProfileSingleProcessPrivilege';
SE_INC_BASE_PRIORITY_NAME         = 'SeIncreaseBasePriorityPrivilege';
SE_CREATE_PAGEFILE_NAME           = 'SeCreatePagefilePrivilege';
SE_CREATE_PERMANENT_NAME          = 'SeCreatePermanentPrivilege';
SE_BACKUP_NAME                    = 'SeBackupPrivilege';
SE_RESTORE_NAME                   = 'SeRestorePrivilege';
SE_SHUTDOWN_NAME                  = 'SeShutdownPrivilege';
SE_DEBUG_NAME                     = 'SeDebugPrivilege';
SE_AUDIT_NAME                     = 'SeAuditPrivilege';
SE_system_ENVIRONMENT_NAME        = 'SesystemEnvironmentPrivilege';
SE_CHANGE_NOTIFY_NAME             = 'SeChangeNotifyPrivilege';
SE_REMOTE_SHUTDOWN_NAME           = 'SeRemoteShutdownPrivilege';

{ ------------------------------------------------- }

{ support standard Error output, besides standard Output/Input }
var
Error: TextFile;

procedure
InitErrorOutput;
begin
AssignFile(Error, EmptyStr);
Rewrite(Error);
TTextRec(Error).Handle := GetStdHandle(STD_ERROR_HANDLE);
end;

var
_TokenizeStr: PChar =  nil;  _TokenizeLast: PChar = nil;

function
Tokenize(const SourceText: string; const Delimiters: string): string;
{ this is my Delphi version of C's strtok():
    1st call: SourceText is not empty, next calls: SourceText is EmptyStr;
set of delimiters can change while tokenizing;
implicit string memory allocation is hidden for the outside:
    Tokenize only parses one SourceText at a time. }
var
R, S: PChar;
begin
if
length(SourceText) = 0 then R:= _TokenizeLast
else
begin
{ cleanup and (re)initialize }
  
_TokenizeLast := nil; StrDispose(_TokenizeStr);
  
_TokenizeStr:= StrNew(PChar(SourceText)); R:= _TokenizeStr;
end;
if
R <> nil then
begin
  
S:= R;   { find next delim }
  
while (S^ <> chr(0)) and (StrScan(PChar(Delimiters), S^) = nil) do
    
inc(S);
   if
S^ <> chr(0) then
   begin
    
S^:= chr(0); { got delim, truncate R result }
    
inc(S);      { skip over delims to set _TokenizeLast }
    
while (S^ <> chr(0)) and (StrScan(PChar(Delimiters), S^) <> nil) do
      
inc(S);
     if
S^ <> chr(0) then _TokenizeLast:= S;
   end;
  
Result:= string(R);
   if
S^ = chr(0) then
   begin    
{ cleanup early }
    
_TokenizeLast:= nil; StrDispose(_TokenizeStr); _TokenizeStr:=nil;
   end
end
else
  
Result:= EmptyStr
end;

{ -------------------------------------------------------------- }
const
DEFWINSTATION: string = 'WinSta0';
DEFDESKTOP: string = 'Default';
WHITESPACE: string = ' '{SPACE}+chr(9){TAB}+chr(10){LF};
DOMUSERSEP: string = '\';
procedure
ErrorHandler (const errmsg: string);
var
err: dword;
begin
err:= GetLastError;
writeln(Error, 'Error: ', errmsg, '.');
write(Error, SysErrorMessage(err));
end;

function
SetUserObjectAllAccess(hUserObject: THANDLE): boolean;
var
pSD: PSecurity_Descriptor;
si:  Security_Information; { dword }
begin
(* Initialize a security descriptor. *)
pSD := PSecurity_Descriptor(
          
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH));
if
pSD = nil then
begin
  
ErrorHandler('Can''t Allocate Local Memory');
  
Result:= FALSE; exit;
end;
if not
InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION) then
begin
  
ErrorHandler('Can''t Initialize Security Descriptor');
  
LocalFree (HLOCAL (pSD) );
  
Result:= FALSE; exit;
end;

(* Add a NULL disc. ACL to the security descriptor. *)
if not SetSecurityDescriptorDacl(pSD,
          
TRUE,     // specifying a disc. ACL
          
PACL (nil),
          
FALSE) then  // not a default disc. ACL
begin
  
ErrorHandler('Can''t Set Security Descriptor DACL');
  
LocalFree (HLOCAL (pSD));
  
Result:= FALSE; exit;
end;

(* Add the security descriptor to the userobject (like a window or a DDE
    conversation), NOT to a kernelobject (like a process, thread or event). *)
si := DACL_SECURITY_INformATION;
Result := SetUserObjectSecurity(hUserObject, si, pSD);

LocalFree(HLOCAL(pSD));
if not
Result then
  
ErrorHandler('Can''t Set NewUser Object Security')
end;
function
GetUserObjectName(hUserObject: THandle; var Name: string): boolean;
var
dw: DWord;
begin
Name:=EmptyStr;
GetUserObjectInformation(hUserObject, UOI_NAME, PChar(Name), 0, dw);
SetLength(Name, dw+1);
Result:= GetUserObjectInformation(hUserObject, UOI_NAME, PChar(Name), dw, dw);
if
Result then SetLength(Name, dw-1)
else
Name:= EmptyStr;
end;

function
GetPrivilegeDisplayName(const PrivilegeName: string): string;
{ PrivilegeName is of string type 'SE_'* }
var dw, li: DWord;
begin
Result:= EmptyStr; dw:= 0; li:=0; { li:= dword(MAKELANGID(LANG_DEFAULT, LANG_USER)); }
if not LookupPrivilegeDisplayName(nil, PChar(PrivilegeName), PChar(Result), dw, li)
then
dw:=256;
SetLength(Result, dw+1);
if
LookupPrivilegeDisplayName(nil, PChar(PrivilegeName), PChar(Result), dw, li)
then
SetLength(Result, StrLen(PChar(Result)))
else
Result:= EmptyStr;
end;

function
GetAccountInfo(var CurUser, CurDomain: string): boolean;
var
dw, dw2: DWord;
pSD: PSecurity_Descriptor;
snu: Sid_Name_Use;
begin
Result:= False;
dw:= 255; Setlength(CurUser, dw+1);
if
GetUserName(PChar(CurUser), dw) then
begin
  
SetLength(CurUser, dw-1);
  
dw2:=256; SetLength(CurDomain, dw2);
  
snu:= SidTypeUser;
  
pSD:= nil; dw:=0; { get needed length for SID }
  
LookUpAccountName(nil{LocalMachine}, PChar(CurUser),
        
pSD, dw, PChar(CurDomain), dw2, snu);
   if
dw <> 0 then
   begin
    
pSD := PSecurity_Descriptor(LocalAlloc(LPTR, dw));
     if
pSD <> nil then
     begin
       if
LookUpAccountName(nil, PChar(CurUser), { get the real thing }
            
pSD, dw, PChar(CurDomain), dw2, snu) then
       begin
        
SetLength(CurDomain, dw2);
        
Result:= True;
       end
       else
CurDomain:= EmptyStr;
      
LocalFree(HLOCAL(pSD));
     end;  
   end;
end
else
CurUser:= EmptyStr;
end;

function
GetMachineName: string;
var
dw: DWord;
begin
dw:=MAX_COMPUTERNAME_LENGTH+1;
SetLength(Result, MAX_COMPUTERNAME_LENGTH+1);
if
GetComputerName(PChar(Result), dw) then SetLength(Result, dw)
else
Result:= EmptyStr;
end;

{ ---------------------------------------------------------- }
var
CurUser,                 // Current User
CurDomain,               // Current Domain
pwstr,                   // password string
consoleTitle,            // Title if new console only
NewDomUser,              // NewDomain\NewUser combination
CommandLine,             // command line we pass to the new process
NewDomain,               // NewDomain to log onto
NewUser: string;         // NewUser to log onto
startUpInfo: TStartupInfo;
procInfo: TProcessInformation;  // child process info, from CreateProcessAsUser
hDesktop: HDESK;
hWindowStation: HWINSTA;
hUserToken, hConsIn: THANDLE;
OldConsInMode, NewConsInMode: DWORD;
NTversion: TOSVersionInfo;
S, DeskTopName, WinStaName: string;
RC: integer;

begin
{ program }
InitErrorOutput; // Attach outputfile Error to STDERR

// Make sure we are using the minimum OS version.
NTversion.dwOSVersionInfoSize:= sizeof (TOSVersionInfo);
if not
GetVersionEx(NTversion) then
begin
  
ErrorHandler('Unable to get OS version');
  
halt(1);
end;
if
NTversion.dwPlatformId <> VER_PLATform_WIN32_NT then
begin
  
writeln(Error, 'SU will run only on Windows NT.');
  
halt(1);
end;
if
NTversion.dwBuildNumber < 1057 then // Commercial 3.51 release
begin
  
writeln(Error, 'SU requires at minimum NT version 3.51 build 1057.');
  
halt(1);
end;

//{$IFDEF DEBUG}
writeln('SU: NT Version ', NTversion.dwMajorVersion, '.',
        
NTversion.dwMinorVersion, ', build ', NTversion.dwBuildNumber);
// {$ENDIF}

GetAccountInfo(CurUser, CurDomain);
writeln('You are ',CurDomain,'\',CurUser);

// Process the command line parameters
            
Tokenize(string(CmdLine), WHITESPACE);
NewDomUser := Tokenize(EmptyStr,        WHITESPACE);
if
length(NewDomUser) = 0 then
begin
  
NewDomUser:= DEFAULT_USER; CommandLine:= DEFAULT_CMD;
end
else
begin
  
CommandLine:= Tokenize(EmptyStr, EmptyStr);
   if
length(CommandLine) = 0 then CommandLine:= DEFAULT_CMD;
end;
if
Pos(DOMUSERSEP, NewDomUser) > 0 then
begin
  
NewDomain := Tokenize(NewDomUser, DOMUSERSEP);
  
NewUser   := Tokenize(EmptyStr, DOMUSERSEP);
   if
length(NewUser) = 0 then NewUser:= DEFAULT_USER;
end
else
begin
  
NewDomain:= EmptyStr; NewUser:= NewDomUser;
end;
if (
length(NewDomain)=0) and
    
((
NewUser= '-?') or (NewUser= '/?') or (NewUser= '?')) then
begin
  
writeln;
  
writeln('Runs Windows NT commands under another user''s account.');
  
writeln;
  
writeln('SU [newdomain\][newuser] [command-line]');
  
writeln;
  
writeln('  [newdomain\]   Specifies desired domain logon (\\ is ok also).');
  
writeln('  [newuser]      Specifies the name of the user to be impersonated.');
  
writeln('                   The default is Administrator.');
  
writeln('  [command-line] Specifies the command to be executed, with parameters.');
  
writeln('                   The default is CMD (a new NT Console).');
  
writeln;
  
writeln('Requires three extended NT privileges:'); writeln;
  
writeln('  ',GetPrivilegeDisplayName(SE_TCB_NAME),',');
  
writeln('  ',GetPrivilegeDisplayName(SE_ASSIGNPRIMARYTOKEN_NAME),' and');
  
writeln('  ',GetPrivilegeDisplayName(SE_INCREASE_QUOTA_NAME),'.');
  
writeln;
  
writeln('These can be granted as User Rights with NT User Manager.');
  
halt(0);
end;

// Turn off console mode echo, since we don't want clear-screen passwords
system.Reset(Input);  {GetStdHandle(STD_INPUT_HANDLE)}
hConsIn:= TTextRec(Input).Handle;

//if hConsIn = INVALID_HANDLE_values then
//begin
//  ErrorHandler ('Can''t get handle of STDIN'); halt(1);
//end;

if not GetConsoleMode(hConsIn, OldConsInMode) then
begin
  
ErrorHandler ('Can''t get current Console Mode'); halt(1);
end;
NewConsInMode:= OldConsInMode and (not ENABLE_ECHO_INPUT);
if not
SetConsoleMode(hConsIn, NewConsInMode) then
begin
  
ErrorHandler ('Unable to turn off Echo'); halt(1);
end;

// Ask for the password
{$IFDEF VERBOSE}
if length(NewDomain) = 0 then S:= CurDomain else S:= NewDomain;
writeln('Logging onto ', S, ' domain as ', NewUser, '.');
{$ENDIF}
write ('Enter password: ');  readln (pwstr);
// When echo is off and NewUser hits , CR-LF is not echoed, so do it for him
writeln;
if not
SetConsoleMode(hConsIn, OldConsInMode) then
begin
  
ErrorHandler ('Unable to reset previous console mode'); halt(1);
end;
CloseHandle (hConsIn);

// Do the Logon
if not LogonUser (PChar(NewUser),PChar(NewDomain),PChar(pwstr),
                  
LOGON32_LOGON_INTERACTIVE,
                  
LOGON32_PROVIDER_DEFAULT, hUserToken) then
begin
   case
GetLastError of
    
ERROR_PRIVILEGE_NOT_HELD:
       begin
        
writeln(Error,
          
'Error: you do not have the following extended User Right:');
        
writeln(Error, GetPrivilegeDisplayName(SE_TCB_NAME),'.');
       end;
    
ERROR_LOGON_FAILURE:
      
ErrorHandler('LogonUser failed.');
    
ERROR_ACCESS_DENIED:
      
ErrorHandler('Access is denied');
     else
      
ErrorHandler('Unable to logon');
     end;
  
halt(2);
end;

// give the NewUser access to the current WindowStation and Desktop
hWindowStation:= GetProcessWindowStation;
if not
GetUserObjectName(hWindowStation, WinStaName) then
  
WinStaName:= DEFWINSTATION;
if not
SetUserObjectAllAccess(hWindowStation) then
begin
  
write(Error, 'Can''t set WindowStation ',WinStaName,' security.');
  
CloseHandle (hUserToken); halt(3);
end;
hDesktop := GetThreadDesktop(GetCurrentThreadId);
if not
GetUserObjectName(hDesktop, DeskTopName) then
  
DeskTopName:= DEFDESKTOP;
if not
SetUserObjectAllAccess(hDesktop) then
begin
  
write(Error, 'Can''t set Desktop ',DeskTopName,' security.');
  
CloseHandle (hUserToken); halt(3);
end;

// Set the STARTUPINFO for the new process
if length(NewDomain) <> 0 then NewDomain:= NewDomain+ '\';
consoleTitle:= 'SU: ' + NewDomain + NewUser;
FillChar(startUpInfo, sizeof(startUpInfo), 0);
with
startUpInfo do
begin
  
cb:= sizeof(startUpInfo); lpTitle:= PChar(consoleTitle);
  
S:= WinStaName+'\'+DeskTopName; lpDesktop:= PChar(S);
end;

// Create the child process
if not CreateProcessAsUser(hUserToken,
           nil,
PChar(CommandLine), nil, nil, FALSE{no inherit handles},
          
CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP ,
           nil, nil,
startUpInfo, procInfo) then
begin
   case
GetLastError of
    
ERROR_PRIVILEGE_NOT_HELD:
       begin
        
writeln(Error, 'Error: missing (one of) following extended User Rights:');
        
writeln(Error, GetPrivilegeDisplayName(SE_ASSIGNPRIMARYTOKEN_NAME),', or');
        
writeln(Error, GetPrivilegeDisplayName(SE_INCREASE_QUOTA_NAME),'.');
        
ErrorHandler(EmptyStr);
       end;
    
ERROR_FILE_NOT_FOUND:
      
ErrorHandler('Error: command in '''+CommandLine+''' not found.');
     else
      
ErrorHandler ('Error: CreateProcessAsUser failed.');
     end;
  
RC:=4;
end
else
  
RC:=0;

CloseHandle(hWindowStation);
CloseHandle(hDesktop);
CloseHandle(hUserToken);

if
RC=0 then
begin
  
CloseHandle(procInfo.hThread);
  
CloseHandle(procInfo.hProcess);
end;  

halt(RC);

end.











Please rate this article!
Skill level:
BeginnerExpert

Useful:
No!Very!

Overall rating:
PoorExcellent



Comments to this article
Write a new comment
Re: Impersonating user on NT/2000...
    Corey Lawson (Aug 6 2002 7:44AM)

With Delphi, there is a command-line utility that will dump all the DLL calls made within a command, TDUMP. This can be handy...

I came up on this when asked to write some scripts to install an IE patch for our work computers, w/o using SMS. Ideally, it would be simple to run via login scripts from our domain controllers. Oh, on NT/2000, it needs to run from an account with Administrator privs.

Searching on Google landed me on this article on the Novell website (www.novell.com/coolsolutions/zenworks/features/ trenches/tr_win2k_profiles_zw.html), that mentioned a DLL that is included in the article, but not really mentioned, or findable, anywhere else on Google, SFImpersonator.DLL. The included VBScript files do work, but they require the user running the VBScript files to be a part of the Administrators (local or domain) group, or have a couple of privileges granted to them additionally. This wasn't feasible for my situation (hundreds of machines)... (note, the 'SU' command that is part of the MKS Utilities essentially works the same way as SFImpersonator.dll, utilizing the same API calls...).

Windows 2000 introduced the RunAs command, but MS chose, for some reason, to make the RunAs command always interactively ask for the password, in a way that makes it unscriptable.

The Novell article also mentioned another company that makes a similar product called TQCRunas, which said it implemented a scriptable implementation of the Runas program. Cool beans! (the trial version is functional). I used tdump to look at the TQCRunas32.dll, and noticed it used a completely different set of API calls, with a lot of user security-related stuff around it.

At this point, I decided I could either write a program to try and implement the same functionality as TQCRunas, or get work to buy the package. It became cost-effective to buy the TQCRunas package.
Which we did, and it has gotten the job done.
Respond

RE: Re: Impersonating user on NT/2000...
Stewart Moss (Aug 7 2002 7:01AM)

Thanks, will be using tdump more often :-)
Respond














 
Sign up to consume product discounts for Bronze memberships !

read more


  Visit our Sponsor

 

  Community Ad of
A. B. Talal
 
   














 







     
  Copyright © 2000 - 2007 delphi3000.com - All rights reserved. Terms of use. || Privacy
delphi3000.com is a service by bluestep.com IT-Services GmbH (Vienna)