unit1.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. unit Unit1;
  2. {*
  3. This source code is provided under the MIT license:
  4. Copyright (C) 2011 by Reinier Olislagers
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in
  12. all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. THE SOFTWARE.
  20. *}
  21. {$mode objfpc}{$H+}
  22. interface
  23. uses
  24. Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  25. StdCtrls, Grids, AsyncProcess, Menus;
  26. type
  27. { TCheckRideMain }
  28. TCheckRideMain = class(TForm)
  29. MainMenu1: TMainMenu;
  30. Memo1: TMemo;
  31. FileMenu: TMenuItem;
  32. HelpMenu: TMenuItem;
  33. AboutMenu: TMenuItem;
  34. MenuLicense: TMenuItem;
  35. QuitMenu: TMenuItem;
  36. TunnelProcess: TAsyncProcess;
  37. VNCGUIProcess: TAsyncProcess;
  38. ConnectButton: TButton;
  39. DisconnectButton: TButton;
  40. ServerPort: TLabeledEdit;
  41. ServerName: TLabeledEdit;
  42. procedure AboutMenuClick(Sender: TObject);
  43. procedure ConnectButtonClick(Sender: TObject);
  44. procedure DisconnectButtonClick(Sender: TObject);
  45. procedure FormActivate(Sender: TObject);
  46. procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  47. procedure FormCreate(Sender: TObject);
  48. procedure ConnectForHelp;
  49. procedure DisconnectHelp;
  50. procedure ShowCommandLineHelp;
  51. procedure MenuLicenseClick(Sender: TObject);
  52. procedure QuitMenuClick(Sender: TObject);
  53. procedure ServerNameEditingDone(Sender: TObject);
  54. procedure ServerPortEditingDone(Sender: TObject);
  55. private
  56. FSetupComplete: boolean;
  57. FHostInCommandLine: boolean; //Does user want to override host using command line?
  58. FPortInCommandLine: boolean; //Does user want to override port using command line?
  59. FNameInCommandLine: boolean;
  60. //Does user want to override helper name using command line?
  61. FConnected: boolean; //whether or not the connection is started
  62. FConnectOneTime: boolean; //Should we connect in the OnActivate event?
  63. FVNCFullPath: string;
  64. FStunnelFullPath: string;
  65. FVNCServiceAlreadyExisted: boolean;
  66. FWeInstalledVNCService: boolean; //Used for cleanup
  67. FVNCServiceWasRunning: boolean; //Shows if VNC service was already running.
  68. procedure SetupConfigAndExes;
  69. procedure Updatebuttons;
  70. { private declarations }
  71. public
  72. { public declarations }
  73. end;
  74. var
  75. CheckRideMain: TCheckRideMain;
  76. implementation
  77. uses
  78. Windows, CheckRideUtil, aboutform;
  79. {$R *.lfm}
  80. // Use explicit manifest file created so we have elevated (admin) privileges on
  81. // Windows systems with UAC on.
  82. // Note: disable in Project Options, uncheck the "Use manifest file to enable themes (windows only)" checkbox
  83. {$R manifest.rc}
  84. { TCheckRideMain }
  85. const
  86. VNCExe = 'winvnc.exe';
  87. UltraVNCServiceName = 'uvnc_service';
  88. StunnelExe = 'stunnel.exe';
  89. procedure TCheckRideMain.ShowCommandLineHelp;
  90. begin
  91. Memo1.Append('Command line options:');
  92. Memo1.Append('-h or --help: show this information.');
  93. Memo1.Append('-x or --noautoconnect: don''t connect automatically.');
  94. Memo1.Append('-i or --helperhost: host name or IP address of helper. Overrides CheckRide.conf');
  95. Memo1.Append('-p or --helperport: helper port number. Overrides CheckRide.conf');
  96. Memo1.Append('-n or --helpername: helper name to be displayed. Overrides CheckRide.conf');
  97. end;
  98. procedure TCheckRideMain.FormCreate(Sender: TObject);
  99. var
  100. ErrorMessage: string = '';
  101. begin
  102. FSetupComplete := False; //Run setup later, in activate
  103. FConnected := False;
  104. FWeInstalledVNCService := False; //We haven't installed the VNC service... yet
  105. FVNCServiceAlreadyExisted := False; //We haven't detected any existing VNC service...
  106. FVNCServiceWasRunning := False; // .. so it isn't running either, as far as we know.
  107. FVNCServiceAlreadyExisted := False;
  108. FConnectOneTime := True;
  109. // Check parameters.
  110. // Host, port and name require parameters.
  111. ErrorMessage := Application.CheckOptions(
  112. 'hxi:p:n:', 'noautoconnect help helperhost: helperport: helpername:');
  113. if Length(ErrorMessage) > 0 then
  114. begin
  115. Memo1.Append('Error: wrong command line options given.');
  116. Memo1.Append(ErrorMessage);
  117. ShowCommandLineHelp;
  118. end;
  119. if Application.HasOption('x', 'noautoconnect') then
  120. begin
  121. FConnectOneTime := False;
  122. end;
  123. if Application.HasOption('h', 'help') then
  124. begin
  125. ShowCommandLineHelp;
  126. end;
  127. // We start with name as we might need to overrule
  128. // FNameInCommandLine:=false later on.
  129. if Application.HasOption('n', 'helpername') then
  130. begin
  131. FNameInCommandLine := True;
  132. end
  133. else
  134. begin
  135. FNameInCommandLine := False;
  136. end;
  137. if Application.HasOption('i', 'helperhost') then
  138. begin
  139. FHostInCommandLine := True;
  140. FNameInCommandLine := True; //Seems logical, it's a different host now.
  141. end
  142. else
  143. begin
  144. FHostInCommandLine := False;
  145. end;
  146. if Application.HasOption('p', 'helperport') then
  147. begin
  148. FPortInCommandLine := True;
  149. FNameInCommandLine := True; //Seems logical, it's a different host now.
  150. end
  151. else
  152. begin
  153. FPortInCommandLine := False;
  154. end;
  155. end;
  156. procedure TCheckRideMain.SetupConfigAndExes;
  157. {description Unpacks resources and sets up configuration. Needs to be called once in application.}
  158. begin
  159. ResourceExtract(ParamStr(0)); //Extract resources, read config etc.
  160. FVNCFullPath := FResourceDir + DirectorySeparator + VNCExe;
  161. FStunnelFullPath := FResourceDir + DirectorySeparator + StunnelExe;
  162. if Trim(ServerName.Text) = '' then
  163. begin
  164. if FHostInCommandLine = True then
  165. begin
  166. // Command line options override config file.
  167. FConnectHost := Trim(Application.GetOptionValue('i', 'helperhost'));
  168. FConnectHelper := 'helper'; //default value
  169. end;
  170. ServerName.Text := FConnectHost;
  171. end;
  172. if Trim(ServerPort.Text) = '' then
  173. begin
  174. if FPortInCommandLine = True then
  175. begin
  176. // Command line options override config file.
  177. FConnectPort := StrToInt(Trim(Application.GetOptionValue('p', 'helperport')));
  178. FConnectHelper := 'helper'; //default value
  179. end;
  180. ServerPort.Text := IntToStr(FConnectPort);
  181. end;
  182. if FNameInCommandLine = True then
  183. begin
  184. FConnectHelper := Application.GetOptionValue('n', 'helpername');
  185. end;
  186. FSetupComplete := True;
  187. end;
  188. procedure TCheckRideMain.ConnectButtonClick(Sender: TObject);
  189. begin
  190. if FConnected = True then
  191. begin
  192. ShowMessage('Already connected. Please disconnect first.');
  193. end
  194. else
  195. begin
  196. ConnectForHelp;
  197. end;
  198. end;
  199. procedure TCheckRideMain.AboutMenuClick(Sender: TObject);
  200. var
  201. TheForm: TInfoAboutForm;
  202. begin
  203. TheForm := aboutform.TInfoAboutForm.Create(Application);
  204. try
  205. TheForm.FileName := 'Readme.txt';
  206. TheForm.ShowModal;
  207. finally
  208. TheForm.Release;
  209. end;
  210. end;
  211. procedure TCheckRideMain.DisconnectButtonClick(Sender: TObject);
  212. begin
  213. if FConnected = False then
  214. begin
  215. ShowMessage('Connection not started, so I can''t disconnect.');
  216. end
  217. else
  218. begin
  219. DisconnectHelp;
  220. end;
  221. end;
  222. procedure TCheckRideMain.FormActivate(Sender: TObject);
  223. begin
  224. // Set up, one time only, hopefully
  225. if FSetupComplete = False then
  226. begin
  227. SetupConfigAndExes; //Updates FSetupComplete.
  228. end;
  229. // Implement autoconnect after form is shown for the first time
  230. if FConnectOneTime = True then
  231. begin
  232. FConnectOneTime := False; //Don't start this again
  233. ConnectButton.Enabled := False; //Don't let user click this
  234. DisconnectButton.Enabled := False; //This just doesn't make sense now, either
  235. Application.ProcessMessages; //Give Lazarus a chance to draw the screen.
  236. ConnectForHelp;
  237. end;
  238. end;
  239. procedure TCheckRideMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  240. begin
  241. // Clean up existing connections
  242. DisconnectHelp;
  243. // Clean up temp dir
  244. CleanTempDir;
  245. end;
  246. procedure TCheckRideMain.DisconnectHelp;
  247. {description Disconnect existing sessions, clean up}
  248. const
  249. SleepTimeOut = 4; //Wait this number of times for service to stop
  250. var
  251. i: integer;
  252. begin
  253. Screen.Cursor := crHourglass;
  254. try
  255. Memo1.Append('Stopping connections to helper.');
  256. // Stop connection
  257. if SysUtils.ExecuteProcess(FVNCFullPath, ' -stopreconnect') <> 0 then
  258. begin
  259. Memo1.Append('Step 1 of 7: Error running winvnc -stopreconnect');
  260. end
  261. else
  262. begin
  263. Memo1.Append('Step 1 of 7: VNC connection attempts stopped.');
  264. end;
  265. if FWeInstalledVNCService = True then
  266. begin
  267. // Stop vnc service, if we have installed it
  268. if SysUtils.ExecuteProcess(FVNCFullPath, ' -stopservice') <> 0 then
  269. begin
  270. Memo1.Append('Step 2 of 7: Error running winvnc -stopservice');
  271. end
  272. else
  273. begin
  274. Memo1.Append('Step 2 of 7: Temporary VNC service stopped.');
  275. end;
  276. // Uninstall service, if we have installed it.
  277. if SysUtils.ExecuteProcess(FVNCFullPath, ' -uninstall') <> 0 then
  278. begin
  279. Memo1.Append('Step 3 of 7: Error running winvnc -uninstall');
  280. end
  281. else
  282. begin
  283. Memo1.Append('Step 3 of 7: Temporary VNC service uninstalled.');
  284. FWeInstalledVNCService := False; //Now it's not installed anymore
  285. end;
  286. end
  287. else
  288. begin
  289. if FVNCServiceAlreadyExisted = True then
  290. begin
  291. if FVNCServiceWasRunning = False then
  292. begin
  293. // Leave it as we found it:
  294. StopService(UltraVNCServiceName);
  295. Memo1.Append(
  296. 'Step 3 of 7: Stopped existing VNC service, to leave it as we found it.');
  297. end;
  298. end;
  299. end;
  300. if VNCGUIProcess.Running = True then
  301. // Kill VNC trayicon GUI
  302. begin
  303. VNCGUIProcess.Terminate(ExitCode);
  304. //We'll wait for it to quit below, so no need to wait here.
  305. Memo1.Append('Step 4 of 7: VNC tray icon closed.');
  306. end;
  307. if TunnelProcess.Running = True then
  308. begin
  309. // Stop our stunnel process
  310. TunnelProcess.Terminate(ExitCode);
  311. // Wait some, apparently needed for terminate to filter through.
  312. Sleep(100); //Should be long enough to get some work done
  313. Application.ProcessMessages;//for good measure
  314. //DON'T MESS WITH FWeStartedStunnel; we'll need it below
  315. Memo1.Append('Step 5 of 7: Asked SSL/TLS tunnel to stop.');
  316. end;
  317. // Make sure that the VNC service is gone after uninstalling
  318. if FWeInstalledVNCService = True then
  319. begin
  320. if IsServiceRunning(UltraVNCServiceName) = True then
  321. begin
  322. // Wait for service to stop
  323. Memo1.Append('Step 6 of 7: Temporary VNC service is still running.');
  324. i := 0;
  325. repeat
  326. sleep(500);
  327. Application.ProcessMessages; //Handle GUI events
  328. i := i + 1;
  329. Memo1.Append('Step 6 of 7: Waiting for temporary VNC service to stop (' +
  330. IntToStr(i) + ')');
  331. if i >= SleepTimeOut then
  332. begin
  333. Memo1.Append(
  334. 'Step 6 of 7: Giving up waiting for temporary VNC service to stop.');
  335. break;
  336. end;
  337. until IsServiceRunning(UltraVNCServiceName) = False;
  338. end;
  339. end;
  340. // Now check that stunnel is down
  341. // note we can check for process names, but what if there are 2 stunnels?
  342. if TunnelProcess.Running = True then
  343. begin
  344. Memo1.Append(
  345. 'Step 7 of 7: Error stopping SSL/TLS tunnel; trying to kill all stunnel.exes with taskkill.');
  346. // Stop stunnel process forcefully
  347. PostMessage(TunnelProcess.Handle, WM_QUIT, 0, 0);
  348. Application.ProcessMessages;
  349. Sleep(500);
  350. if TunnelProcess.Running = True then
  351. begin
  352. if TerminateProcess(TunnelProcess.Handle, 255) then
  353. begin
  354. Memo1.Append('Step 7 of 7: Finished stopping SSL/TLS tunnel.');
  355. end
  356. else
  357. begin
  358. Memo1.Append('Step 7 of 7: Error stopping SSL/TLS tunnel: ' + StunnelExe);
  359. end;
  360. end;
  361. end
  362. else
  363. begin
  364. Memo1.Append('Step 7 of 7: SSL/TLS tunnel is stopped.');
  365. end;
  366. try
  367. CleanSystemTray; //Get rid of zombie icons
  368. except
  369. on E: Exception do
  370. begin
  371. Memo1.Append('Step 7 of 7: small problem cleaning up icons. Details:' +
  372. E.ClassName + '/' + E.Message);
  373. end;
  374. end;
  375. FConnected := False;
  376. //might not be totally true if disconnect attempts above failed...
  377. Memo1.Append('Disconnected.');
  378. Updatebuttons;
  379. Screen.Cursor := crDefault;
  380. except
  381. on E: Exception do
  382. begin
  383. Memo1.Append('Error running commands; error was ' + E.ClassName + '/' + E.Message);
  384. Updatebuttons;
  385. Screen.Cursor := crDefault;
  386. end;
  387. end;
  388. end;
  389. procedure TCheckRideMain.MenuLicenseClick(Sender: TObject);
  390. var
  391. TheForm: TInfoAboutForm; //TForm is not specialised enough
  392. begin
  393. TheForm := aboutform.TInfoAboutForm.Create(Application);
  394. try
  395. TheForm.FileName := 'License.txt';
  396. TheForm.ShowModal;
  397. finally
  398. TheForm.Release;
  399. end;
  400. end;
  401. procedure TCheckRideMain.QuitMenuClick(Sender: TObject);
  402. begin
  403. Close;
  404. end;
  405. procedure TCheckRideMain.ServerNameEditingDone(Sender: TObject);
  406. begin
  407. CheckRideUtil.FConnectHost := Trim(ServerName.Text);
  408. end;
  409. procedure TCheckRideMain.ServerPortEditingDone(Sender: TObject);
  410. begin
  411. CheckRideUtil.FConnectPort := StrToIntDef(ServerPort.Text, 3334);
  412. end;
  413. procedure TCheckRideMain.Updatebuttons;
  414. begin
  415. ConnectButton.Enabled := not (FConnected);
  416. DisconnectButton.Enabled := FConnected;
  417. end;
  418. procedure TCheckRideMain.ConnectForHelp;
  419. {description Connect to helper}
  420. { TODO 6 -oAnyone -cNice to have: refactor vnc into separate unit/class, also stunnel }
  421. const
  422. // Note the leading space to separate command from options/parameters
  423. // Note that this "autoreconnect" param MUST be BEFORE the "connect" on the command line
  424. // (see UltraVNC winvnc.cpp)
  425. // Officially, I think you should run -servicehelper, but that doesn't seem
  426. // to do anything.
  427. VNCParameters = ' -autoreconnect -connect 127.0.0.1::65000';
  428. SleepTimeOut = 4;
  429. var
  430. i: integer;
  431. procedure PrepareToExit(Message: string);
  432. {description Revert GUI level changes so we can exit the procedure cleanly}
  433. begin
  434. Screen.Cursor := crDefault;
  435. UpdateButtons;
  436. Memo1.Append(Message);
  437. end;
  438. begin
  439. ConnectButton.Enabled := False; //Don't let user click twice.
  440. Screen.Cursor := crHourglass;
  441. Memo1.Append('Setting up connection to helper ' + FConnectHelper +
  442. ' at ' + FConnectHost + ':' + IntToStr(FConnectPort) + '.');
  443. // Start up stunnel asynchronously: let it run in parallel with the rest of the program
  444. try
  445. TunnelProcess.CommandLine :=
  446. FStunnelFullPath + ' ' + CustomSTunnelconfig(Helped) + '';
  447. TunnelProcess.Execute;
  448. Memo1.Append('Step 1 of 5: Started SSL/TLS tunnel.');
  449. except
  450. PrepareToExit('Step 1 of 5: Error running ' + FStunnelFullPath +
  451. ' ' + CustomSTunnelConfig(Helped) + '');
  452. exit; //exit procedure, useless to continue
  453. end;
  454. try
  455. if ServiceExists(UltraVNCServiceName) then
  456. begin
  457. FVNCServiceAlreadyExisted := True;
  458. if IsServiceRunning(UltraVNCServiceName) then
  459. begin
  460. FVNCServiceWasRunning := True;
  461. Memo1.Append('Step 2 of 5: Existing running VNC service detected.');
  462. end
  463. else
  464. begin
  465. FVNCServiceWasRunning := False;
  466. Memo1.Append('Step 2 of 5: Existing VNC service detected.');
  467. end;
  468. end
  469. else
  470. begin
  471. FVNCServiceAlreadyExisted := False;
  472. end;
  473. except
  474. on E: Exception do
  475. begin
  476. //do nothing, just report the error
  477. Memo1.Append('Error checking existing VNC service. Details: ' +
  478. E.ClassName + '/' + E.Message);
  479. end;
  480. end;
  481. try
  482. // Install vnc service, required for ctr-alt-del support on at least Vista+
  483. FWeInstalledVNCService := False; //default
  484. if FVNCServiceAlreadyExisted = False then
  485. begin
  486. // Only do this if there isn't a vnc service already present
  487. Memo1.Append('Step 2 of 5: Installing temporary VNC service.');
  488. if SysUtils.ExecuteProcess(FVNCFullPath, ' -install') <> 0 then
  489. begin
  490. Memo1.Append('Error running ' + FVNCFullPath + ' -install');
  491. end
  492. else
  493. begin
  494. FWeInstalledVNCService := True;
  495. Memo1.Append('Step 2 of 5: Finished installing temporary VNC service.');
  496. end;
  497. end
  498. else
  499. begin
  500. // Service already existed.
  501. if FVNCServiceWasRunning = False then
  502. begin
  503. // Start up VNC service
  504. CheckRideUtil.StartService(UltraVNCServiceName);
  505. Memo1.Append(
  506. 'Step 2 of 5: Found and started existing VNC service. This might not work, though.');
  507. end;
  508. end;
  509. if TunnelProcess.Running = True then
  510. begin
  511. //Let's not get the user confused.
  512. //Memo1.Append('Tunnel program running.');
  513. end
  514. else
  515. begin
  516. Memo1.Append('Step 3 of 5: Tunnel not running yet. We might have problems later on.');
  517. end;
  518. //Wait for vnc service to come up
  519. if IsServiceRunning(UltraVNCServiceName) = False then
  520. begin
  521. // Wait for service to come up
  522. i := 0;
  523. repeat
  524. sleep(500);
  525. Application.ProcessMessages; //Handle
  526. i := i + 1;
  527. Memo1.Append('Step 3 of 5: Waiting for temporary VNC service to start up (' +
  528. IntToStr(i) + ')');
  529. if i >= SleepTimeOut then
  530. break;
  531. until IsServiceRunning(UltraVNCServiceName) = True;
  532. end
  533. else
  534. begin
  535. Memo1.Append('Step 3 of 5: Temporary VNC service is running.');
  536. end;
  537. // Now, it seems we need some extra wait time otherwise
  538. // vnc command won't work
  539. Memo1.Append('Step 4 of 5: Waiting 10 seconds for temporary VNC service to get ready.');
  540. Application.ProcessMessages;
  541. sleep(10000);
  542. Application.ProcessMessages;
  543. // Actual connect. Don't use service parameter for this
  544. try
  545. VNCGUIProcess.CommandLine :=
  546. FVNCFullPath + ' ' + VNCParameters + '';
  547. VNCGUIProcess.Execute;
  548. FConnected := True;
  549. Memo1.Append('Step 5 of 5: Started VNC connection attempt.');
  550. Memo1.Append('Done.');
  551. Memo1.Append('Waiting for helper to take over...');
  552. PrepareToExit('If helper takes over, the VNC eye symbol close to the clock will change color.');
  553. except
  554. PrepareToExit('Step 1 of 5: Error running ' + FVNCFullPath +
  555. ' ' + VNCParameters + '');
  556. exit; //exit procedure, useless to continue
  557. end;
  558. except
  559. on E: Exception do
  560. begin
  561. PrepareToExit('Error running commands; error was ' + E.ClassName +
  562. '/' + E.Message);
  563. Memo1.Append('Cleaning up.');
  564. DisconnectHelp;
  565. end;
  566. end;
  567. end;
  568. end.