You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

723 lines
18 KiB
Plaintext

{
Copyright 2017 Stas'M Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
}
unit MainUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Spin, ExtCtrls, Registry, WinSvc;
type
TMainForm = class(TForm)
bOK: TButton;
bCancel: TButton;
bApply: TButton;
cbSingleSessionPerUser: TCheckBox;
rgNLA: TRadioGroup;
cbAllowTSConnections: TCheckBox;
rgShadow: TRadioGroup;
seRDPPort: TSpinEdit;
lRDPPort: TLabel;
lService: TLabel;
lListener: TLabel;
lWrapper: TLabel;
lsListener: TLabel;
lsService: TLabel;
lsWrapper: TLabel;
Timer: TTimer;
lTSVer: TLabel;
lsTSVer: TLabel;
lWrapVer: TLabel;
lsWrapVer: TLabel;
bLicense: TButton;
gbDiag: TGroupBox;
lsSuppVer: TLabel;
cbHideUsers: TCheckBox;
gbGeneral: TGroupBox;
cbCustomPrg: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure cbAllowTSConnectionsClick(Sender: TObject);
procedure seRDPPortChange(Sender: TObject);
procedure bApplyClick(Sender: TObject);
procedure bCancelClick(Sender: TObject);
procedure bOKClick(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure bLicenseClick(Sender: TObject);
procedure TimerTimer(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
function ExecWait(Cmdline: String): Boolean;
procedure ReadSettings;
procedure WriteSettings;
end;
FILE_VERSION = record
Version: record case Boolean of
True: (dw: DWORD);
False: (w: record
Minor, Major: Word;
end;)
end;
Release, Build: Word;
bDebug, bPrerelease, bPrivate, bSpecial: Boolean;
end;
WTS_SESSION_INFOW = record
SessionId: DWORD;
Name: packed array [0..33] of WideChar;
State: DWORD;
end;
WTS_SESSION = Array[0..0] of WTS_SESSION_INFOW;
PWTS_SESSION_INFOW = ^WTS_SESSION;
const
winstadll = 'winsta.dll';
var
MainForm: TMainForm;
Ready: Boolean = False;
Arch: Byte;
OldWow64RedirectionValue: LongBool;
OldPort: Word;
INI: String;
function WinStationEnumerateW(hServer: THandle;
var ppSessionInfo: PWTS_SESSION_INFOW; var pCount: DWORD): BOOL; stdcall;
external winstadll name 'WinStationEnumerateW';
function WinStationFreeMemory(P: Pointer): BOOL; stdcall; external winstadll;
implementation
{$R *.dfm}
{$R resource.res}
uses
LicenseUnit;
function ExpandPath(Path: String): String;
var
Str: Array[0..511] of Char;
begin
Result := '';
FillChar(Str, 512, 0);
if Arch = 64 then
Path := StringReplace(Path, '%ProgramFiles%', '%ProgramW6432%', [rfReplaceAll, rfIgnoreCase]);
if ExpandEnvironmentStrings(PWideChar(Path), Str, 512) > 0 then
Result := Str;
end;
function DisableWowRedirection: Boolean;
type
TFunc = function(var Wow64FsEnableRedirection: LongBool): LongBool; stdcall;
var
hModule: THandle;
Wow64DisableWow64FsRedirection: TFunc;
begin
Result := False;
hModule := GetModuleHandle(kernel32);
if hModule <> 0 then
Wow64DisableWow64FsRedirection := GetProcAddress(hModule, 'Wow64DisableWow64FsRedirection')
else
Exit;
if @Wow64DisableWow64FsRedirection <> nil then
Result := Wow64DisableWow64FsRedirection(OldWow64RedirectionValue);
end;
function RevertWowRedirection: Boolean;
type
TFunc = function(var Wow64RevertWow64FsRedirection: LongBool): LongBool; stdcall;
var
hModule: THandle;
Wow64RevertWow64FsRedirection: TFunc;
begin
Result := False;
hModule := GetModuleHandle(kernel32);
if hModule <> 0 then
Wow64RevertWow64FsRedirection := GetProcAddress(hModule, 'Wow64RevertWow64FsRedirection')
else
Exit;
if @Wow64RevertWow64FsRedirection <> nil then
Result := Wow64RevertWow64FsRedirection(OldWow64RedirectionValue);
end;
function GetFileVersion(const FileName: TFileName; var FileVersion: FILE_VERSION): Boolean;
type
VS_VERSIONINFO = record
wLength, wValueLength, wType: Word;
szKey: Array[1..16] of WideChar;
Padding1: Word;
Value: VS_FIXEDFILEINFO;
Padding2, Children: Word;
end;
PVS_VERSIONINFO = ^VS_VERSIONINFO;
const
VFF_DEBUG = 1;
VFF_PRERELEASE = 2;
VFF_PRIVATE = 8;
VFF_SPECIAL = 32;
var
hFile: HMODULE;
hResourceInfo: HRSRC;
VersionInfo: PVS_VERSIONINFO;
begin
Result := False;
hFile := LoadLibraryEx(PWideChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE);
if hFile = 0 then
Exit;
hResourceInfo := FindResource(hFile, PWideChar(1), PWideChar($10));
if hResourceInfo = 0 then
Exit;
VersionInfo := Pointer(LoadResource(hFile, hResourceInfo));
if VersionInfo = nil then
Exit;
FileVersion.Version.dw := VersionInfo.Value.dwFileVersionMS;
FileVersion.Release := Word(VersionInfo.Value.dwFileVersionLS shr 16);
FileVersion.Build := Word(VersionInfo.Value.dwFileVersionLS);
FileVersion.bDebug := (VersionInfo.Value.dwFileFlags and VFF_DEBUG) = VFF_DEBUG;
FileVersion.bPrerelease := (VersionInfo.Value.dwFileFlags and VFF_PRERELEASE) = VFF_PRERELEASE;
FileVersion.bPrivate := (VersionInfo.Value.dwFileFlags and VFF_PRIVATE) = VFF_PRIVATE;
FileVersion.bSpecial := (VersionInfo.Value.dwFileFlags and VFF_SPECIAL) = VFF_SPECIAL;
FreeLibrary(hFile);
Result := True;
end;
function IsWrapperInstalled(var WrapperPath: String): ShortInt;
var
TermServiceHost,
TermServicePath: String;
Reg: TRegistry;
begin
Result := -1;
WrapperPath := '';
Reg := TRegistry.Create;
Reg.RootKey := HKEY_LOCAL_MACHINE;
if not Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\TermService') then begin
Reg.Free;
Exit;
end;
TermServiceHost := Reg.ReadString('ImagePath');
Reg.CloseKey;
if Pos('svchost.exe', LowerCase(TermServiceHost)) = 0 then
begin
Result := 2;
Reg.Free;
Exit;
end;
if not Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\TermService\Parameters') then
begin
Reg.Free;
Exit;
end;
TermServicePath := Reg.ReadString('ServiceDll');
Reg.CloseKey;
Reg.Free;
if (Pos('termsrv.dll', LowerCase(TermServicePath)) = 0)
and (Pos('rdpwrap.dll', LowerCase(TermServicePath)) = 0) then
begin
Result := 2;
Exit;
end;
if Pos('rdpwrap.dll', LowerCase(TermServicePath)) > 0 then begin
WrapperPath := TermServicePath;
Result := 1;
end else
Result := 0;
end;
function GetTermSrvState: ShortInt;
type
SERVICE_STATUS_PROCESS = record
dwServiceType,
dwCurrentState,
dwControlsAccepted,
dwWin32ExitCode,
dwServiceSpecificExitCode,
dwCheckPoint,
dwWaitHint,
dwProcessId,
dwServiceFlags: DWORD;
end;
PSERVICE_STATUS_PROCESS = ^SERVICE_STATUS_PROCESS;
const
SvcName = 'TermService';
var
hSC: SC_HANDLE;
hSvc: THandle;
lpServiceStatusProcess: PSERVICE_STATUS_PROCESS;
Buf: Pointer;
cbBufSize, pcbBytesNeeded: Cardinal;
begin
Result := -1;
hSC := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
if hSC = 0 then
Exit;
hSvc := OpenService(hSC, PWideChar(SvcName), SERVICE_QUERY_STATUS);
if hSvc = 0 then
begin
CloseServiceHandle(hSC);
Exit;
end;
if QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO, nil, 0, pcbBytesNeeded) then
Exit;
cbBufSize := pcbBytesNeeded;
GetMem(Buf, cbBufSize);
if not QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO, Buf, cbBufSize, pcbBytesNeeded) then begin
FreeMem(Buf, cbBufSize);
CloseServiceHandle(hSvc);
CloseServiceHandle(hSC);
Exit;
end else begin
lpServiceStatusProcess := Buf;
Result := ShortInt(lpServiceStatusProcess^.dwCurrentState);
end;
FreeMem(Buf, cbBufSize);
CloseServiceHandle(hSvc);
CloseServiceHandle(hSC);
end;
function IsListenerWorking: Boolean;
var
pCount: DWORD;
SessionInfo: PWTS_SESSION_INFOW;
I: Integer;
begin
Result := False;
if not WinStationEnumerateW(0, SessionInfo, pCount) then
Exit;
for I := 0 to pCount - 1 do
if SessionInfo^[I].Name = 'RDP-Tcp' then begin
Result := True;
Break;
end;
WinStationFreeMemory(SessionInfo);
end;
function ExtractResText(ResName: String): String;
var
ResStream: TResourceStream;
Str: TStringList;
begin
ResStream := TResourceStream.Create(HInstance, ResName, RT_RCDATA);
Str := TStringList.Create;
try
Str.LoadFromStream(ResStream);
except
end;
ResStream.Free;
Result := Str.Text;
Str.Free;
end;
function TMainForm.ExecWait(Cmdline: String): Boolean;
var
si: STARTUPINFO;
pi: PROCESS_INFORMATION;
begin
Result := False;
ZeroMemory(@si, sizeof(si));
si.cb := sizeof(si);
si.dwFlags := STARTF_USESHOWWINDOW;
si.wShowWindow := SW_HIDE;
UniqueString(Cmdline);
if not CreateProcess(nil, PWideChar(Cmdline), nil, nil, True, 0, nil, nil, si, pi) then begin
MessageBox(Handle,
PWideChar('CreateProcess error (code: ' + IntToStr(GetLastError) + ').'),
'Error', MB_ICONERROR or MB_OK);
Exit;
end;
CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
Result := True;
end;
procedure TMainForm.ReadSettings;
var
Reg: TRegistry;
SecurityLayer, UserAuthentication: Integer;
begin
Reg := TRegistry.Create;
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Control\Terminal Server');
try
cbAllowTSConnections.Checked := not Reg.ReadBool('fDenyTSConnections');
except
end;
try
cbSingleSessionPerUser.Checked := Reg.ReadBool('fSingleSessionPerUser');
except
end;
try
cbCustomPrg.Checked := Reg.ReadBool('HonorLegacySettings');
except
end;
Reg.CloseKey;
Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp');
seRDPPort.Value := 3389;
try
seRDPPort.Value := Reg.ReadInteger('PortNumber');
except
end;
OldPort := seRDPPort.Value;
SecurityLayer := 0;
UserAuthentication := 0;
try
SecurityLayer := Reg.ReadInteger('SecurityLayer');
UserAuthentication := Reg.ReadInteger('UserAuthentication');
except
end;
if (SecurityLayer = 0) and (UserAuthentication = 0) then
rgNLA.ItemIndex := 0;
if (SecurityLayer = 1) and (UserAuthentication = 0) then
rgNLA.ItemIndex := 1;
if (SecurityLayer = 2) and (UserAuthentication = 1) then
rgNLA.ItemIndex := 2;
try
rgShadow.ItemIndex := Reg.ReadInteger('Shadow');
except
end;
Reg.CloseKey;
Reg.OpenKeyReadOnly('\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System');
try
cbHideUsers.Checked := Reg.ReadBool('dontdisplaylastusername');
except
end;
Reg.CloseKey;
Reg.Free;
end;
procedure TMainForm.WriteSettings;
var
Reg: TRegistry;
SecurityLayer, UserAuthentication: Integer;
begin
Reg := TRegistry.Create;
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server', True);
try
Reg.WriteBool('fDenyTSConnections', not cbAllowTSConnections.Checked);
except
end;
try
Reg.WriteBool('fSingleSessionPerUser', cbSingleSessionPerUser.Checked);
except
end;
try
Reg.WriteBool('HonorLegacySettings', cbCustomPrg.Checked);
except
end;
Reg.CloseKey;
Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp', True);
try
Reg.WriteInteger('PortNumber', seRDPPort.Value);
except
end;
if OldPort <> seRDPPort.Value then
begin
OldPort := seRDPPort.Value;
ExecWait('netsh advfirewall firewall set rule name="Remote Desktop" new localport=' + IntToStr(OldPort));
end;
case rgNLA.ItemIndex of
0: begin
SecurityLayer := 0;
UserAuthentication := 0;
end;
1: begin
SecurityLayer := 1;
UserAuthentication := 0;
end;
2: begin
SecurityLayer := 2;
UserAuthentication := 1;
end;
else begin
SecurityLayer := -1;
UserAuthentication := -1;
end;
end;
if SecurityLayer >= 0 then begin
try
Reg.WriteInteger('SecurityLayer', SecurityLayer);
Reg.WriteInteger('UserAuthentication', UserAuthentication);
except
end;
end;
if rgShadow.ItemIndex >= 0 then begin
try
Reg.WriteInteger('Shadow', rgShadow.ItemIndex);
except
end;
end;
Reg.CloseKey;
Reg.OpenKey('\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services', True);
if rgShadow.ItemIndex >= 0 then begin
try
Reg.WriteInteger('Shadow', rgShadow.ItemIndex);
except
end;
end;
Reg.CloseKey;
Reg.OpenKey('\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System', True);
try
Reg.WriteBool('dontdisplaylastusername', cbHideUsers.Checked);
except
end;
Reg.CloseKey;
Reg.Free;
end;
function CheckSupport(FV: FILE_VERSION): Byte;
var
VerTxt: String;
begin
Result := 0;
if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 0) then
Result := 1;
if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 1) then
Result := 1;
VerTxt := Format('%d.%d.%d.%d',
[FV.Version.w.Major, FV.Version.w.Minor, FV.Release, FV.Build]);
if Pos('[' + VerTxt + ']', INI) > 0 then
Result := 2;
end;
procedure TMainForm.TimerTimer(Sender: TObject);
var
WrapperPath, INIPath: String;
FV: FILE_VERSION;
L: TStringList;
CheckSupp: Boolean;
begin
CheckSupp := False;
case IsWrapperInstalled(WrapperPath) of
-1: begin
lsWrapper.Caption := 'Unknown';
lsWrapper.Font.Color := clGrayText;
end;
0: begin
lsWrapper.Caption := 'Not installed';
lsWrapper.Font.Color := clGrayText;
end;
1: begin
lsWrapper.Caption := 'Installed';
lsWrapper.Font.Color := clGreen;
CheckSupp := True;
INIPath := ExtractFilePath(ExpandPath(WrapperPath)) + 'rdpwrap.ini';
if not FileExists(INIPath) then
CheckSupp := False;
end;
2: begin
lsWrapper.Caption := '3rd-party';
lsWrapper.Font.Color := clRed;
end;
end;
case GetTermSrvState of
-1, 0: begin
lsService.Caption := 'Unknown';
lsService.Font.Color := clGrayText;
end;
SERVICE_STOPPED: begin
lsService.Caption := 'Stopped';
lsService.Font.Color := clRed;
end;
SERVICE_START_PENDING: begin
lsService.Caption := 'Starting...';
lsService.Font.Color := clGrayText;
end;
SERVICE_STOP_PENDING: begin
lsService.Caption := 'Stopping...';
lsService.Font.Color := clGrayText;
end;
SERVICE_RUNNING: begin
lsService.Caption := 'Running';
lsService.Font.Color := clGreen;
end;
SERVICE_CONTINUE_PENDING: begin
lsService.Caption := 'Resuming...';
lsService.Font.Color := clGrayText;
end;
SERVICE_PAUSE_PENDING: begin
lsService.Caption := 'Suspending...';
lsService.Font.Color := clGrayText;
end;
SERVICE_PAUSED: begin
lsService.Caption := 'Suspended';
lsService.Font.Color := clWindowText;
end;
end;
if IsListenerWorking then begin
lsListener.Caption := 'Listening';
lsListener.Font.Color := clGreen;
end else begin
lsListener.Caption := 'Not listening';
lsListener.Font.Color := clRed;
end;
if WrapperPath = '' then begin
lsWrapVer.Caption := 'N/A';
lsWrapVer.Font.Color := clGrayText;
end else
if not GetFileVersion(ExpandPath(WrapperPath), FV) then begin
lsWrapVer.Caption := 'N/A';
lsWrapVer.Font.Color := clGrayText;
end else begin
lsWrapVer.Caption :=
IntToStr(FV.Version.w.Major)+'.'+
IntToStr(FV.Version.w.Minor)+'.'+
IntToStr(FV.Release)+'.'+
IntToStr(FV.Build);
lsWrapVer.Font.Color := clWindowText;
end;
if not GetFileVersion('termsrv.dll', FV) then begin
lsTSVer.Caption := 'N/A';
lsTSVer.Font.Color := clGrayText;
end else begin
lsTSVer.Caption :=
IntToStr(FV.Version.w.Major)+'.'+
IntToStr(FV.Version.w.Minor)+'.'+
IntToStr(FV.Release)+'.'+
IntToStr(FV.Build);
lsTSVer.Font.Color := clWindowText;
lsSuppVer.Visible := CheckSupp;
if CheckSupp then begin
if INI = '' then begin
L := TStringList.Create;
try
L.LoadFromFile(INIPath);
except
end;
INI := L.Text;
L.Free;
end;
case CheckSupport(FV) of
0: begin
lsSuppVer.Caption := '[not supported]';
lsSuppVer.Font.Color := clRed;
end;
1: begin
lsSuppVer.Caption := '[supported partially]';
lsSuppVer.Font.Color := clOlive;
end;
2: begin
lsSuppVer.Caption := '[fully supported]';
lsSuppVer.Font.Color := clGreen;
end;
end;
end;
end;
end;
procedure TMainForm.bLicenseClick(Sender: TObject);
begin
LicenseForm.mText.Text := ExtractResText('LICENSE');
if LicenseForm.ShowModal <> mrOk then
Halt(0);
end;
procedure TMainForm.cbAllowTSConnectionsClick(Sender: TObject);
begin
if Ready then
bApply.Enabled := True;
end;
procedure TMainForm.seRDPPortChange(Sender: TObject);
begin
if Ready then
bApply.Enabled := True;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
SI: TSystemInfo;
begin
GetNativeSystemInfo(SI);
case SI.wProcessorArchitecture of
0: Arch := 32;
6: Arch := 64; // Itanium-based x64
9: Arch := 64; // Intel/AMD x64
else Arch := 0;
end;
if Arch = 64 then
DisableWowRedirection;
ReadSettings;
Ready := True;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
if Arch = 64 then
RevertWowRedirection;
end;
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if bApply.Enabled then
CanClose := MessageBox(Handle, 'Settings are not saved. Do you want to exit?',
'Warning', mb_IconWarning or mb_YesNo) = mrYes;
end;
procedure TMainForm.bOKClick(Sender: TObject);
begin
if bApply.Enabled then begin
WriteSettings;
bApply.Enabled := False;
end;
Close;
end;
procedure TMainForm.bCancelClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.bApplyClick(Sender: TObject);
begin
WriteSettings;
bApply.Enabled := False;
end;
end.