checkridehelperunit.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. unit CheckRideHelperUnit;
  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. { TCheckRideHelperMain }
  28. TCheckRideHelperMain = class(TForm)
  29. MainMenu1: TMainMenu;
  30. FileMenu: TMenuItem;
  31. HelpMenu: TMenuItem;
  32. About: TMenuItem;
  33. ToolsMenu: Tmenuitem;
  34. CustomizeMenu: Tmenuitem;
  35. MenuLicenses: TMenuItem;
  36. QuitMenu: TMenuItem;
  37. PortScanButton: TButton;
  38. WhatIsMyIPButton: TButton;
  39. Memo1: TMemo;
  40. TunnelProcess: TAsyncProcess;
  41. ConnectButton: TButton;
  42. DisconnectButton: TButton;
  43. ServerPort: TLabeledEdit;
  44. VNCViewerProcess: TAsyncProcess;
  45. procedure AboutClick(Sender: TObject);
  46. procedure ConnectButtonClick(Sender: TObject);
  47. procedure DisconnectButtonClick(Sender: TObject);
  48. procedure FormActivate(Sender: TObject);
  49. procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  50. procedure FormCreate(Sender: TObject);
  51. procedure CustomizeMenuClick(Sender: TObject);
  52. procedure MenuLicensesClick(Sender: TObject);
  53. procedure PortScanButtonClick(Sender: TObject);
  54. procedure QuitMenuClick(Sender: TObject);
  55. procedure SetupConfigAndExes;
  56. procedure ListenForHelp;
  57. procedure DisconnectHelp;
  58. procedure ShowCommandLineHelp;
  59. procedure ServerPortEditingDone(Sender: TObject);
  60. procedure WhatIsMyIPButtonClick(Sender: TObject);
  61. private
  62. FExePath: string;
  63. FPortInCommandLine: boolean; //Does user want to override port using command line?
  64. FListenOneTime: boolean; //Should we start listening in the OnActivate event?
  65. FSetupComplete: boolean;
  66. FVNCViewerFullPath: string;
  67. FStunnelFullPath: string;
  68. FListening: boolean; //Is the tunnel/vnc combo running and listening?
  69. procedure Updatebuttons;
  70. { private declarations }
  71. public
  72. { public declarations }
  73. end;
  74. var
  75. CheckRideHelperMain: TCheckRideHelperMain;
  76. implementation
  77. uses
  78. Windows, CheckRideUtil, LCLIntf, aboutform, clientcustomizer;
  79. {$R *.lfm}
  80. { TCheckRideHelperMain }
  81. const
  82. VNCViewerExe = 'vncviewer.exe';
  83. StunnelExe = 'stunnel.exe';
  84. VNCViewerListenPort = 65001;
  85. procedure TCheckRideHelperMain.FormCreate(Sender: TObject);
  86. var
  87. ErrorMessage: string;
  88. begin
  89. FSetupComplete := False;
  90. FListening := False;//We're not listening yet
  91. FExePath := ExtractFilePath(Application.ExeName); //or ExtractFilePath(ParamStr(0))
  92. FVNCViewerFullPath := FExePath + VNCViewerExe; //FExePath already has trailing \
  93. FStunnelFullPath := FExePath + StunnelExe;
  94. //Find out if we need to start listening in the OnActivate event:
  95. FListenOneTime := True;
  96. // Check parameters.
  97. // Host, port and name require parameters.
  98. ErrorMessage := Application.CheckOptions('hxp:', 'noautoconnect help helperport:');
  99. if Length(ErrorMessage) > 0 then
  100. begin
  101. Memo1.Append('Error: wrong command line options given:');
  102. Memo1.Append(ErrorMessage);
  103. ShowCommandLineHelp;
  104. end;
  105. if Application.HasOption('x', 'noautoconnect') then
  106. begin
  107. FListenOneTime := False;
  108. end;
  109. if Application.HasOption('h', 'help') then
  110. begin
  111. ShowCommandLineHelp;
  112. end;
  113. if Application.HasOption('p', 'helperport') then
  114. begin
  115. FPortInCommandLine := True;
  116. end
  117. else
  118. begin
  119. FPortInCommandLine := False;
  120. end;
  121. end;
  122. procedure Tcheckridehelpermain.CustomizeMenuClick(Sender: TObject);
  123. var
  124. TheForm: TForm;
  125. begin
  126. TheForm := clientcustomizer.TfrmClientCustomizer.Create(Application);
  127. try
  128. TheForm.ShowModal;
  129. finally
  130. TheForm.Release; //free will be done by LCL code
  131. end;
  132. end;
  133. procedure TCheckRideHelperMain.MenuLicensesClick(Sender: TObject);
  134. var
  135. TheForm: TInfoAboutForm; //We need more precision than just TForm
  136. begin
  137. TheForm := aboutform.TInfoAboutForm.Create(Application);
  138. try
  139. TheForm.Filename := 'License.txt';
  140. TheForm.ShowModal;
  141. finally
  142. TheForm.Release;
  143. end;
  144. end;
  145. procedure TCheckRideHelperMain.PortScanButtonClick(Sender: TObject);
  146. {description Open web browser to let user portscan own machine}
  147. const
  148. URL = 'http://nmap-online.com/';
  149. begin
  150. OpenURL(URL);
  151. end;
  152. procedure TCheckRideHelperMain.QuitMenuClick(Sender: TObject);
  153. begin
  154. Close;
  155. end;
  156. procedure TCheckRideHelperMain.Updatebuttons;
  157. begin
  158. ConnectButton.Enabled := not (FListening);
  159. DisconnectButton.Enabled := FListening;
  160. end;
  161. procedure TCheckRideHelperMain.ConnectButtonClick(Sender: TObject);
  162. begin
  163. if FListening = True then
  164. begin
  165. ShowMessage('Already connected. Please disconnect first.');
  166. end
  167. else
  168. begin
  169. ListenForHelp;
  170. end;
  171. end;
  172. procedure TCheckRideHelperMain.AboutClick(Sender: TObject);
  173. var
  174. TheForm: TInfoAboutForm; //We need more precision than just TForm
  175. begin
  176. TheForm := aboutform.TInfoAboutForm.Create(Application);
  177. try
  178. TheForm.Filename := 'readme.txt';
  179. TheForm.ShowModal;
  180. finally
  181. TheForm.Release;
  182. end;
  183. end;
  184. procedure TCheckRideHelperMain.DisconnectButtonClick(Sender: TObject);
  185. begin
  186. if FListening = False then
  187. begin
  188. ShowMessage('Connection not started, so I can''t disconnect.');
  189. end
  190. else
  191. begin
  192. DisconnectHelp;
  193. end;
  194. end;
  195. procedure TCheckRideHelperMain.SetupConfigAndExes;
  196. {description Unpacks resources and sets up configuration. Needs to be called once in application.}
  197. begin
  198. // Get resources from this executable.
  199. // Note: won't be guaranteed portable to Linux/OSX
  200. ResourceExtract(ParamStr(0));
  201. if Trim(ServerPort.Text) = '' then
  202. begin
  203. if FPortInCommandLine = True then
  204. begin
  205. // Command line options override config file.
  206. FConnectPort := StrToInt(Trim(Application.GetOptionValue('p', 'helperport')));
  207. //Then we also override the rest
  208. FConnectHelper := 'command line options';
  209. FConnectHost := 'without name';
  210. end;
  211. ServerPort.Text := IntToStr(checkrideutil.FConnectPort);
  212. end;
  213. FSetupComplete := True;
  214. end;
  215. procedure TCheckRideHelperMain.FormActivate(Sender: TObject);
  216. begin
  217. // Set up, one time only, hopefully
  218. if FSetupComplete = False then
  219. begin
  220. SetupConfigAndExes; //Updates FSetupComplete.
  221. end;
  222. if FListenOneTime = True then
  223. begin
  224. FListenOneTime := False; //only do it once
  225. ConnectButton.Enabled := False; //Don't let user click this
  226. DisconnectButton.Enabled := False; //This just doesn't make sense now, either
  227. Application.ProcessMessages; //Give Lazarus a chance to draw the screen.
  228. ListenForHelp; //Actual connect/listen action
  229. end;
  230. end;
  231. procedure TCheckRideHelperMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  232. begin
  233. // Clean up existing connections
  234. DisconnectHelp;
  235. // Clean up temp dir
  236. CleanTempDir;
  237. end;
  238. procedure TCheckRideHelperMain.DisconnectHelp;
  239. var
  240. ExitCode: integer = 0;
  241. begin
  242. Screen.Cursor := crHourglass;
  243. DisconnectButton.Enabled := False; //Don't let user click any more
  244. try
  245. // Stop/kill viewer
  246. if VNCViewerProcess.Running = True then
  247. begin
  248. VNCViewerProcess.Terminate(ExitCode);
  249. end;
  250. //Stop/kill tunnel
  251. if TunnelProcess.Running = True then
  252. begin
  253. TunnelProcess.Terminate(ExitCode);
  254. end;
  255. //Check again after spending some time closing stunnel
  256. if VNCViewerProcess.Running = True then
  257. begin
  258. //Wait a bit for viewer to close
  259. Application.ProcessMessages;
  260. Sleep(200);
  261. if VNCViewerProcess.Running = True then
  262. begin
  263. //If it won't go nicely, then force it...
  264. PostMessage(VNCViewerProcess.Handle, WM_QUIT, 0, 0);
  265. Application.ProcessMessages;
  266. Sleep(500);
  267. if VNCViewerProcess.Running = True then
  268. begin
  269. if TerminateProcess(VNCViewerProcess.Handle, 255) then
  270. begin
  271. Memo1.Append('Finished killing ' + VNCViewerExe + ' process.');
  272. end
  273. else
  274. begin
  275. Memo1.Append('Error killing ' + VNCViewerExe +
  276. ' process. Please stop the program yourself.');
  277. end;
  278. end;
  279. end
  280. else
  281. begin
  282. Memo1.Append('Finished stopping ' + VNCViewerExe);
  283. end;
  284. end
  285. else
  286. begin
  287. Memo1.Append('Finished stopping ' + VNCViewerExe);
  288. end;
  289. //Check after possibly spending some time closing
  290. //VNCViewer
  291. if TunnelProcess.Running = True then
  292. begin
  293. //Wait a bit for tunnel to close
  294. Application.ProcessMessages;
  295. Sleep(200);
  296. if TunnelProcess.Running = True then
  297. begin
  298. // Stop stunnel
  299. if SysUtils.ExecuteProcess(FindDefaultExecutablePath('taskkill.exe'),
  300. ' /f /t /im ' + StunnelExe + '') <> 0 then
  301. begin
  302. Memo1.Append('Error running taskkill.exe /f /t /im ' + StunnelExe);
  303. end
  304. else
  305. begin
  306. Memo1.Append('Finished killing stunnel');
  307. end;
  308. end
  309. else
  310. begin
  311. Memo1.Append('Finished stopping ' + StunnelExe);
  312. end;
  313. end
  314. else
  315. begin
  316. //Tunnel had already stopped
  317. Memo1.Append('Finished stopping ' + StunnelExe);
  318. end;
  319. try
  320. CleanSystemTray; //Get rid of zombie icons
  321. except
  322. on E: Exception do
  323. begin
  324. Memo1.Append('Small problem cleaning up icons. Details:' +
  325. E.ClassName + '/' + E.Message);
  326. end;
  327. end;
  328. FListening := False;
  329. Memo1.Append('Remote support session ended.');
  330. UpdateButtons;
  331. Screen.Cursor := crDefault;
  332. except
  333. on E: Exception do
  334. begin
  335. FListening := False; //Let's assume things were closed
  336. UpdateButtons;
  337. Memo1.Append('Error running commands; error was ' + E.ClassName + '/' + E.Message);
  338. Screen.Cursor := crDefault;
  339. end;
  340. end;
  341. end;
  342. procedure TCheckRideHelperMain.ShowCommandLineHelp;
  343. begin
  344. Memo1.Append('Command line options:');
  345. Memo1.Append('-h or --help: show this information.');
  346. Memo1.Append('-x or --noautoconnect: don''t connect/listen automatically.');
  347. Memo1.Append(
  348. '-p or --helperport: helper port number that we listen on. Overrides CheckRide.conf');
  349. end;
  350. procedure TCheckRideHelperMain.ServerPortEditingDone(Sender: TObject);
  351. begin
  352. CheckRideUtil.FConnectPort := StrToIntDef(ServerPort.Text, 3334);
  353. end;
  354. procedure TCheckRideHelperMain.WhatIsMyIPButtonClick(Sender: TObject);
  355. {description Open web browser to show external IP}
  356. const
  357. URL = 'http://automation.whatismyip.com/n09230945.asp';
  358. begin
  359. OpenURL(URL);
  360. end;
  361. procedure TCheckRideHelperMain.ListenForHelp;
  362. {description This does the actual work }
  363. var
  364. VNCViewerParameters: string;
  365. LogFile: string;
  366. begin
  367. // note: include space in front of option
  368. // interesting options gleaned from vncviewer -help
  369. // /8bit or /64colors: for improved connection speed.
  370. // Don't know what enablecache does, but it sounds good!
  371. // /listen 33334 listens on specified port, so you shouldn't have anything listening on that port
  372. // /proxy proxyhost [portnum]
  373. // /encoding zrle|zywrle|tight|zlib|zlibhex|ultra => also found raw rre corre hextile ultra2 in source (VNCOptions.cpp, near line 672)
  374. // ultra2 might be useful; doesn't seem to work well though, see below
  375. // /autoacceptincoming automatically accept incoming connections
  376. // /socketkeepalivetimeout n
  377. // /enablecache
  378. // /autoacceptnodsm: useful for ignoring encryption if you have specified a DSM. Not useful to us ;)
  379. // Finally, we have to specify /quickoption 8 to force manual parameters instead of auto.
  380. // /quickoption
  381. // 1: auto mode (zrle, full colors, cache)
  382. // 2: LAN (hextile, full colors, no cache)
  383. // 3: medium (zrle, 256 colors, no cache)
  384. // 4: modem (zrle, 64 colors, cache)
  385. // 5: slow (zrle, 8 colors, cache)
  386. // 7: ULTRA_LAN (ultra enc, full color)
  387. // 8: apparently manual
  388. // apparently you can also disable auto mode by specifying noauto
  389. // NOTE: at least in 1.0.9.6.1 and earlier:
  390. // vncviewer -listen 65001 -quickoption 8 -8bit -encoding ultra2 -enablecache -disablesponsor -autoacceptincoming -autoacceptnodsm -loglevel 10 -logfile C:\<somewhere>TMP00015.tmp
  391. // seems to give a crash in WinVNC: an unhandled Win32 exception occurred in WinVNC [724]
  392. //
  393. // Previous versions of this code used quickoption 3, which caused text entries/fonts in command windows to not appear on the screen
  394. // and other update anomalies.
  395. LogFile := SysUtils.GetTempFileName();
  396. VNCViewerParameters :=
  397. ' ' + '-listen ' + IntToStr(VNCViewerListenPort) + ' -quickoption 8 ' +
  398. ' -encoding zywrle ' +
  399. ' -disablesponsor -autoacceptincoming -autoacceptnodsm -loglevel 10 -logfile '
  400. + LogFile;
  401. if TunnelProcess.Running = True then
  402. begin
  403. ShowMessage('Tunnel has already been started. Stop tunnel before connecting.');
  404. Exit;
  405. end;
  406. ConnectButton.Enabled := False; //Don't let user click twice
  407. Memo1.Append('Reading profile ' + FConnectHelper + ' for server ' +
  408. FConnectHost + ' listening on port ' + IntToStr(FConnectPort));
  409. // Start up stunnel asynchronously: let it run in parallel with the rest of the program
  410. try
  411. TunnelProcess.CommandLine :=
  412. FStunnelFullPath + ' ' + CustomStunnelconfig(Helper) + '';
  413. TunnelProcess.Execute;
  414. Memo1.Append('Finished setting up SSL/TLS tunnel listening on port ' +
  415. IntToStr(CheckRideUtil.FConnectPort));
  416. except
  417. Screen.Cursor := crDefault;
  418. Memo1.Append('Error running ' + FStunnelFullPath + ' stunnelhelper.conf');
  419. exit; //exit procedure, useless to continue
  420. end;
  421. try
  422. if TunnelProcess.Running = False then;
  423. begin
  424. Memo1.Append('Waiting 5 seconds for stunnel to come up.');
  425. sleep(5000);
  426. end;
  427. // Start listening viewer. Don't use service parameter for this
  428. try
  429. VNCViewerProcess.CommandLine := FVNCViewerFullPath + VNCViewerParameters;
  430. VNCViewerProcess.Execute;
  431. FListening := True;
  432. Memo1.Append('Finished setting up VNCViewer listening to tunnel.');
  433. Memo1.Append('Information: parameters used: ' + VNCViewerParameters);
  434. Memo1.Append('VNCViewer log file: ' + logfile);
  435. except
  436. FListening := False;
  437. Screen.Cursor := crDefault;
  438. Memo1.Append('Error running ' + FVNCViewerFullPath + ' ' + VNCViewerParameters);
  439. exit; //exit procedure, useless to continue
  440. end;
  441. UpdateButtons;
  442. Memo1.Append('Listening VNCViewer started...');
  443. Screen.Cursor := crDefault;
  444. except
  445. on E: Exception do
  446. begin
  447. //Assume listening so we can clean up
  448. FListening := True;
  449. Screen.Cursor := crDefault;
  450. Memo1.Append('Error running commands; error was ' + E.ClassName + '/' + E.Message);
  451. Memo1.Append('Cleaning up.');
  452. DisconnectHelp;
  453. end;
  454. end;
  455. end;
  456. end.