Author: Stewart Moss
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.
1
2 {*
3 SU.DPR for Delphi32 Pascal
4 by Fred - APIKing - de Jong, Heerlen, Netherlands 1997
5 home: frejon@worldonline.nl, office: fjng@cbs.nl
6
7 su.cpp
8 UNIX-like Substitute User for Windows NT
9
10 Usage:
11 su [NewDomain\][NewUser] [command-line]
12 where:
13 NewDomain\ is desired domain logon (\\ is ok also)
14 NewUser is the name of the user to be impersonated. Default is Administrator.
15 command-line is the command to be executed, with parameters. Default is CMD
16 (Console)
17
18 Authors:
19 David Wihl (wihl@shore.net)
20 Steffen Krause (skrause@informatik.hu-berlin.de)
21
22 Revision History:
23 xx-JUL-1995.
24 - Removed restriction on command line (User can now specify anything)
25 - Added NewDomain logon on command line
26 - Added Unicode support but found bug in LogonUserW
27 03-JUL-1995. Initial public release
28
29 Design:
30 Impersonating a User on Windows NT is a three step process:
31 1- Logon the User to create a Security identifier
32 2- Enabling access to the Windows Station so the newly logged on NewUser
33 can interact. This is necessary even if the Administrator is logging on.
34 3- Creating a process using the Security identifier
35
36 Different privileges are required for steps (1) and (3). Logging on a User
37 (LogonUser()) requires the SeTcbPrivilege. Creating a process as another User
38 CreateProcessAsUser()) requires SeAssignPrimary and SeIncreaseQuota privileges.
39 To grant these privileges, see the Installation Section.
40
41 These two Security API calls were only stablized in NT 3.51, build 1057. SU will
42 not work with earlier versions.
43
44 In NT, there is no direct equivalent of UNIX's rwsr-xr-x file permission.
45
46 Restrictions and Limitations:
47 - There is no logging of failed or successful usage. A future may incorporate
48 writing to the Event Log.
49
50 Installation:
51 The easiest way to selectively grant the three privileges required to use this
52 program is:
53
54 1- Start the User Manager (MUSRMGR)
55 2- Create a new group (e.g. "SU Users")
56 3- Add the three privileges to the group (via Policies\User Rights):
57 "Act as part of the operating system" - SeTcbPrivilege
58 "Increase quotas" - SeIncreaseQuota
59 "Replace a process level token" - SeAssignPrimaryToken
60
61 NOTE: The three privileges will only be visible if you check
62 "Show Advanced User Rights" in the dialog box.
63 4- Add the desired users to the new group (via User\Properties\Group)
64
65 This program was compiled under Visual C++ 2.1 with the June '95 SDK
66
67 For more information about Porting from UNIX to NT check the FAQ:
68 http://www.shore.net/~wihl/unix2nt.html
69
70 *}
71
72 program su;
73 {$APPTYPE CONSOLE}
74
75 uses
76 Windows,
77 SysUtils { already has SysErrorMessage function };
78
79 //{$R VersInfo.RES}
80
81 //
82 // CUSTOMIZATION OPTIONS - put 'em here
83 const
84 DEFAULT_USER: string = 'Administrator';
85 // if we don't specify a username, who are we?
86 DEFAULT_CMD: string = 'cmd'; // if we don't specify a command, what do we do?
87 {$DEFINE VERBOSE} // quiet ?la UNIX, or chatty?
88 //
89 // END CUSTOMIZATION OPTIONS
90 //
91 const
92 SECURITY_DESCRIPTOR_REVISION = 1; // from winnt.h, missing in windows.pas
93 ////////////////////////////////////////////////////////////////////////
94 // //
95 // NT Defined Privileges //
96 // //
97 ////////////////////////////////////////////////////////////////////////
98 SE_CREATE_TOKEN_NAME = 'SeCreateTokenPrivilege';
99 SE_ASSIGNPRIMARYTOKEN_NAME = 'SeAssignPrimaryTokenPrivilege';
100 SE_LOCK_MEMORY_NAME = 'SeLockMemoryPrivilege';
101 SE_INCREASE_QUOTA_NAME = 'SeIncreaseQuotaPrivilege';
102 SE_UNSOLICITED_INPUT_NAME = 'SeUnsolicitedInputPrivilege';
103 SE_MACHINE_ACCOUNT_NAME = 'SeMachineAccountPrivilege';
104 SE_TCB_NAME = 'SeTcbPrivilege';
105 SE_SECURITY_NAME = 'SeSecurityPrivilege';
106 SE_TAKE_OWNERSHIP_NAME = 'SeTakeOwnershipPrivilege';
107 SE_LOAD_DRIVER_NAME = 'SeLoadDriverPrivilege';
108 SE_system_PROFILE_NAME = 'SesystemProfilePrivilege';
109 SE_systemTIME_NAME = 'SesystemtimePrivilege';
110 SE_PROF_SINGLE_PROCESS_NAME = 'SeProfileSingleProcessPrivilege';
111 SE_INC_BASE_PRIORITY_NAME = 'SeIncreaseBasePriorityPrivilege';
112 SE_CREATE_PAGEFILE_NAME = 'SeCreatePagefilePrivilege';
113 SE_CREATE_PERMANENT_NAME = 'SeCreatePermanentPrivilege';
114 SE_BACKUP_NAME = 'SeBackupPrivilege';
115 SE_RESTORE_NAME = 'SeRestorePrivilege';
116 SE_SHUTDOWN_NAME = 'SeShutdownPrivilege';
117 SE_DEBUG_NAME = 'SeDebugPrivilege';
118 SE_AUDIT_NAME = 'SeAuditPrivilege';
119 SE_system_ENVIRONMENT_NAME = 'SesystemEnvironmentPrivilege';
120 SE_CHANGE_NOTIFY_NAME = 'SeChangeNotifyPrivilege';
121 SE_REMOTE_SHUTDOWN_NAME = 'SeRemoteShutdownPrivilege';
122
123 { ------------------------------------------------- }
124
125 { support standard Error output, besides standard Output/Input }
126 var
127 Error: TextFile;
128
129 procedure InitErrorOutput;
130 begin
131 AssignFile(Error, EmptyStr);
132 Rewrite(Error);
133 TTextRec(Error).Handle := GetStdHandle(STD_ERROR_HANDLE);
134 end;
135
136 var
137 _TokenizeStr: PChar = nil;
138 _TokenizeLast: PChar = nil;
139
140 function Tokenize(const SourceText: string; const Delimiters: string): string;
141 { this is my Delphi version of C's strtok():
142 1st call: SourceText is not empty, next calls: SourceText is EmptyStr;
143 set of delimiters can change while tokenizing;
144 implicit string memory allocation is hidden for the outside:
145 Tokenize only parses one SourceText at a time. }
146 var
147 R, S: PChar;
148 begin
149 if length(SourceText) = 0 then
150 R := _TokenizeLast
151 else
152 begin { cleanup and (re)initialize }
153 _TokenizeLast := nil;
154 StrDispose(_TokenizeStr);
155 _TokenizeStr := StrNew(PChar(SourceText));
156 R := _TokenizeStr;
157 end;
158 if R <> nil then
159 begin
160 S := R; { find next delim }
161 while (S^ <> chr(0)) and (StrScan(PChar(Delimiters), S^) = nil) do
162 inc(S);
163 if S^ <> chr(0) then
164 begin
165 S^ := chr(0); { got delim, truncate R result }
166 inc(S); { skip over delims to set _TokenizeLast }
167 while (S^ <> chr(0)) and (StrScan(PChar(Delimiters), S^) <> nil) do
168 inc(S);
169 if S^ <> chr(0) then
170 _TokenizeLast := S;
171 end;
172 Result := string(R);
173 if S^ = chr(0) then
174 begin { cleanup early }
175 _TokenizeLast := nil;
176 StrDispose(_TokenizeStr);
177 _TokenizeStr := nil;
178 end
179 end
180 else
181 Result := EmptyStr
182 end;
183
184 { -------------------------------------------------------------- }
185 const
186 DEFWINSTATION: string = 'WinSta0';
187 DEFDESKTOP: string = 'Default';
188 WHITESPACE: string = ' ' {SPACE} + chr(9) {TAB} + chr(10) {LF};
189 DOMUSERSEP: string = '\';
190
191 procedure ErrorHandler(const errmsg: string);
192 var
193 err: dword;
194 begin
195 err := GetLastError;
196 writeln(Error, 'Error: ', errmsg, '.');
197 write(Error, SysErrorMessage(err));
198 end;
199
200 function SetUserObjectAllAccess(hUserObject: THANDLE): boolean;
201 var
202 pSD: PSecurity_Descriptor;
203 si: Security_Information; { dword }
204 begin
205 (* Initialize a security descriptor. *)
206 pSD := PSecurity_Descriptor(
207 LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH));
208 if pSD = nil then
209 begin
210 ErrorHandler('Can''t Allocate Local Memory');
211 Result := FALSE;
212 exit;
213 end;
214 if not InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION) then
215 begin
216 ErrorHandler('Can''t Initialize Security Descriptor');
217 LocalFree(HLOCAL(pSD));
218 Result := FALSE;
219 exit;
220 end;
221
222 {* Add a NULL disc. ACL to the security descriptor. *}
223 if not SetSecurityDescriptorDacl(pSD,
224 TRUE, // specifying a disc. ACL
225 PACL(nil),
226 FALSE) then // not a default disc. ACL
227 begin
228 ErrorHandler('Can''t Set Security Descriptor DACL');
229 LocalFree(HLOCAL(pSD));
230 Result := FALSE;
231 exit;
232 end;
233
234 {* Add the security descriptor to the userobject (like a window or a DDE
235 conversation), NOT to a kernelobject (like a process, thread or event). *}
236 si := DACL_SECURITY_INformATION;
237 Result := SetUserObjectSecurity(hUserObject, si, pSD);
238
239 LocalFree(HLOCAL(pSD));
240 if not Result then
241 ErrorHandler('Can''t Set NewUser Object Security')
242 end;
243
244 function GetUserObjectName(hUserObject: THandle; var Name: string): boolean;
245 var
246 dw: DWord;
247 begin
248 Name := EmptyStr;
249 GetUserObjectInformation(hUserObject, UOI_NAME, PChar(Name), 0, dw);
250 SetLength(Name, dw + 1);
251 Result := GetUserObjectInformation(hUserObject, UOI_NAME, PChar(Name), dw, dw);
252 if Result then
253 SetLength(Name, dw - 1)
254 else
255 Name := EmptyStr;
256 end;
257
258 function GetPrivilegeDisplayName(const PrivilegeName: string): string;
259 { PrivilegeName is of string type 'SE_'* }
260 var
261 dw, li: DWord;
262 begin
263 Result := EmptyStr;
264 dw := 0;
265 li := 0; { li:= dword(MAKELANGID(LANG_DEFAULT, LANG_USER)); }
266 if not LookupPrivilegeDisplayName(nil, PChar(PrivilegeName), PChar(Result), dw,
267 li)
268 then
269 dw := 256;
270 SetLength(Result, dw + 1);
271 if LookupPrivilegeDisplayName(nil, PChar(PrivilegeName), PChar(Result), dw, li)
272 then
273 SetLength(Result, StrLen(PChar(Result)))
274 else
275 Result := EmptyStr;
276 end;
277
278 function GetAccountInfo(var CurUser, CurDomain: string): boolean;
279 var
280 dw, dw2: DWord;
281 pSD: PSecurity_Descriptor;
282 snu: Sid_Name_Use;
283 begin
284 Result := False;
285 dw := 255;
286 Setlength(CurUser, dw + 1);
287 if GetUserName(PChar(CurUser), dw) then
288 begin
289 SetLength(CurUser, dw - 1);
290 dw2 := 256;
291 SetLength(CurDomain, dw2);
292 snu := SidTypeUser;
293 pSD := nil;
294 dw := 0; { get needed length for SID }
295 LookUpAccountName(nil {LocalMachine}, PChar(CurUser),
296 pSD, dw, PChar(CurDomain), dw2, snu);
297 if dw <> 0 then
298 begin
299 pSD := PSecurity_Descriptor(LocalAlloc(LPTR, dw));
300 if pSD <> nil then
301 begin
302 if LookUpAccountName(nil, PChar(CurUser), { get the real thing }
303 pSD, dw, PChar(CurDomain), dw2, snu) then
304 begin
305 SetLength(CurDomain, dw2);
306 Result := True;
307 end
308 else
309 CurDomain := EmptyStr;
310 LocalFree(HLOCAL(pSD));
311 end;
312 end;
313 end
314 else
315 CurUser := EmptyStr;
316 end;
317
318 function GetMachineName: string;
319 var
320 dw: DWord;
321 begin
322 dw := MAX_COMPUTERNAME_LENGTH + 1;
323 SetLength(Result, MAX_COMPUTERNAME_LENGTH + 1);
324 if GetComputerName(PChar(Result), dw) then
325 SetLength(Result, dw)
326 else
327 Result := EmptyStr;
328 end;
329
330 { ---------------------------------------------------------- }
331 var
332 CurUser, // Current User
333 CurDomain, // Current Domain
334 pwstr, // password string
335 consoleTitle, // Title if new console only
336 NewDomUser, // NewDomain\NewUser combination
337 CommandLine, // command line we pass to the new process
338 NewDomain, // NewDomain to log onto
339 NewUser: string; // NewUser to log onto
340 startUpInfo: TStartupInfo;
341 procInfo: TProcessInformation; // child process info, from CreateProcessAsUser
342 hDesktop: HDESK;
343 hWindowStation: HWINSTA;
344 hUserToken, hConsIn: THANDLE;
345 OldConsInMode, NewConsInMode: DWORD;
346 NTversion: TOSVersionInfo;
347 S, DeskTopName, WinStaName: string;
348 RC: integer;
349
350 begin { program }
351 InitErrorOutput; // Attach outputfile Error to STDERR
352
353 // Make sure we are using the minimum OS version.
354 NTversion.dwOSVersionInfoSize := sizeof(TOSVersionInfo);
355 if not GetVersionEx(NTversion) then
356 begin
357 ErrorHandler('Unable to get OS version');
358 halt(1);
359 end;
360 if NTversion.dwPlatformId <> VER_PLATform_WIN32_NT then
361 begin
362 writeln(Error, 'SU will run only on Windows NT.');
363 halt(1);
364 end;
365 if NTversion.dwBuildNumber < 1057 then // Commercial 3.51 release
366 begin
367 writeln(Error, 'SU requires at minimum NT version 3.51 build 1057.');
368 halt(1);
369 end;
370
371 //{$IFDEF DEBUG}
372 writeln('SU: NT Version ', NTversion.dwMajorVersion, '.',
373 NTversion.dwMinorVersion, ', build ', NTversion.dwBuildNumber);
374 // {$ENDIF}
375
376 GetAccountInfo(CurUser, CurDomain);
377 writeln('You are ', CurDomain, '\', CurUser);
378
379 // Process the command line parameters
380 Tokenize(string(CmdLine), WHITESPACE);
381 NewDomUser := Tokenize(EmptyStr, WHITESPACE);
382 if length(NewDomUser) = 0 then
383 begin
384 NewDomUser := DEFAULT_USER;
385 CommandLine := DEFAULT_CMD;
386 end
387 else
388 begin
389 CommandLine := Tokenize(EmptyStr, EmptyStr);
390 if length(CommandLine) = 0 then
391 CommandLine := DEFAULT_CMD;
392 end;
393 if Pos(DOMUSERSEP, NewDomUser) > 0 then
394 begin
395 NewDomain := Tokenize(NewDomUser, DOMUSERSEP);
396 NewUser := Tokenize(EmptyStr, DOMUSERSEP);
397 if length(NewUser) = 0 then
398 NewUser := DEFAULT_USER;
399 end
400 else
401 begin
402 NewDomain := EmptyStr;
403 NewUser := NewDomUser;
404 end;
405 if (length(NewDomain) = 0) and
406 ((NewUser = '-?') or (NewUser = '/?') or (NewUser = '?')) then
407 begin
408 writeln;
409 writeln('Runs Windows NT commands under another user''s account.');
410 writeln;
411 writeln('SU [newdomain\][newuser] [command-line]');
412 writeln;
413 writeln(' [newdomain\] Specifies desired domain logon (\\ is ok also).');
414 writeln(' [newuser] Specifies the name of the user to be impersonated.');
415 writeln(' The default is Administrator.');
416 writeln(' [command-line] Specifies the command to be executed, with
417 parameters.'
418 writeln(' The default is CMD (a new NT Console).');
419 writeln;
420 writeln('Requires three extended NT privileges:');
421 writeln;
422 writeln(' ', GetPrivilegeDisplayName(SE_TCB_NAME), ',');
423 writeln(' ', GetPrivilegeDisplayName(SE_ASSIGNPRIMARYTOKEN_NAME), ' and');
424 writeln(' ', GetPrivilegeDisplayName(SE_INCREASE_QUOTA_NAME), '.');
425 writeln;
426 writeln('These can be granted as User Rights with NT User Manager.');
427 halt(0);
428 end;
429
430 // Turn off console mode echo, since we don't want clear-screen passwords
431 system.Reset(Input); {GetStdHandle(STD_INPUT_HANDLE)}
432 hConsIn := TTextRec(Input).Handle;
433
434 //if hConsIn = INVALID_HANDLE_values then
435 //begin
436 // ErrorHandler ('Can''t get handle of STDIN'); halt(1);
437 //end;
438
439 if not GetConsoleMode(hConsIn, OldConsInMode) then
440 begin
441 ErrorHandler('Can''t get current Console Mode');
442 halt(1);
443 end;
444 NewConsInMode := OldConsInMode and (not ENABLE_ECHO_INPUT);
445 if not SetConsoleMode(hConsIn, NewConsInMode) then
446 begin
447 ErrorHandler('Unable to turn off Echo');
448 halt(1);
449 end;
450
451 // Ask for the password
452 {$IFDEF VERBOSE}
453 if length(NewDomain) = 0 then
454 S := CurDomain
455 else
456 S := NewDomain;
457 writeln('Logging onto ', S, ' domain as ', NewUser, '.');
458 {$ENDIF}
459 write('Enter password: ');
460 readln(pwstr);
461 // When echo is off and NewUser hits , CR-LF is not echoed, so do it for him
462 writeln;
463 if not SetConsoleMode(hConsIn, OldConsInMode) then
464 begin
465 ErrorHandler('Unable to reset previous console mode');
466 halt(1);
467 end;
468 CloseHandle(hConsIn);
469
470 // Do the Logon
471 if not LogonUser(PChar(NewUser), PChar(NewDomain), PChar(pwstr),
472 LOGON32_LOGON_INTERACTIVE,
473 LOGON32_PROVIDER_DEFAULT, hUserToken) then
474 begin
475 case GetLastError of
476 ERROR_PRIVILEGE_NOT_HELD:
477 begin
478 writeln(Error,
479 'Error: you do not have the following extended User Right:');
480 writeln(Error, GetPrivilegeDisplayName(SE_TCB_NAME), '.');
481 end;
482 ERROR_LOGON_FAILURE:
483 ErrorHandler('LogonUser failed.');
484 ERROR_ACCESS_DENIED:
485 ErrorHandler('Access is denied');
486 else
487 ErrorHandler('Unable to logon');
488 end;
489 halt(2);
490 end;
491
492 // give the NewUser access to the current WindowStation and Desktop
493 hWindowStation := GetProcessWindowStation;
494 if not GetUserObjectName(hWindowStation, WinStaName) then
495 WinStaName := DEFWINSTATION;
496 if not SetUserObjectAllAccess(hWindowStation) then
497 begin
498 write(Error, 'Can''t set WindowStation ', WinStaName, ' security.');
499 CloseHandle(hUserToken);
500 halt(3);
501 end;
502 hDesktop := GetThreadDesktop(GetCurrentThreadId);
503 if not GetUserObjectName(hDesktop, DeskTopName) then
504 DeskTopName := DEFDESKTOP;
505 if not SetUserObjectAllAccess(hDesktop) then
506 begin
507 write(Error, 'Can''t set Desktop ', DeskTopName, ' security.');
508 CloseHandle(hUserToken);
509 halt(3);
510 end;
511
512 // Set the STARTUPINFO for the new process
513 if length(NewDomain) <> 0 then
514 NewDomain := NewDomain + '\';
515 consoleTitle := 'SU: ' + NewDomain + NewUser;
516 FillChar(startUpInfo, sizeof(startUpInfo), 0);
517 with startUpInfo do
518 begin
519 cb := sizeof(startUpInfo);
520 lpTitle := PChar(consoleTitle);
521 S := WinStaName + '\' + DeskTopName;
522 lpDesktop := PChar(S);
523 end;
524
525 // Create the child process
526 if not CreateProcessAsUser(hUserToken,
527 nil, PChar(CommandLine), nil, nil, FALSE {no inherit handles},
528 CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP,
529 nil, nil, startUpInfo, procInfo) then
530 begin
531 case GetLastError of
532 ERROR_PRIVILEGE_NOT_HELD:
533 begin
534 writeln(Error, 'Error: missing (one of) following extended User Rights:');
535 writeln(Error, GetPrivilegeDisplayName(SE_ASSIGNPRIMARYTOKEN_NAME), ',
536 or'
537 writeln(Error, GetPrivilegeDisplayName(SE_INCREASE_QUOTA_NAME), '.');
538 ErrorHandler(EmptyStr);
539 end;
540 ERROR_FILE_NOT_FOUND:
541 ErrorHandler('Error: command in ''' + CommandLine + ''' not found.');
542 else
543 ErrorHandler('Error: CreateProcessAsUser failed.');
544 end;
545 RC := 4;
546 end
547 else
548 RC := 0;
549
550 CloseHandle(hWindowStation);
551 CloseHandle(hDesktop);
552 CloseHandle(hUserToken);
553
554 if RC = 0 then
555 begin
556 CloseHandle(procInfo.hThread);
557 CloseHandle(procInfo.hProcess);
558 end;
559
560 halt(RC);
561
562 end.
|