ExeSessionProcess.cs 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Threading;
  7. #if !NETSTANDARD
  8. using Microsoft.Win32;
  9. #endif
  10. using Microsoft.Win32.SafeHandles;
  11. using System.Runtime.InteropServices;
  12. using System.Reflection;
  13. #if !NETSTANDARD
  14. using System.Security.Principal;
  15. using System.Security.AccessControl;
  16. #endif
  17. using System.ComponentModel;
  18. using System.Security.Cryptography;
  19. using System.Linq;
  20. namespace WinSCP
  21. {
  22. internal class ExeSessionProcess : IDisposable
  23. {
  24. public event OutputDataReceivedEventHandler OutputDataReceived;
  25. public bool HasExited { get { return _process.HasExited; } }
  26. public int ExitCode { get { return _process.ExitCode; } }
  27. public PipeStream StdOut { get; set; }
  28. public Stream StdIn { get; set; }
  29. public string ExecutablePath { get; }
  30. public static ExeSessionProcess CreateForSession(Session session)
  31. {
  32. return new ExeSessionProcess(session, true, null);
  33. }
  34. public static ExeSessionProcess CreateForConsole(Session session, string additionalArguments)
  35. {
  36. return new ExeSessionProcess(session, false, additionalArguments);
  37. }
  38. private ExeSessionProcess(Session session, bool useXmlLog, string additionalArguments)
  39. {
  40. _session = session;
  41. _logger = session.Logger;
  42. _incompleteLine = string.Empty;
  43. using (_logger.CreateCallstack())
  44. {
  45. ExecutablePath = GetExecutablePath();
  46. _logger.WriteLine("EXE executable path resolved to {0}", ExecutablePath);
  47. string assemblyFilePath = _logger.GetAssemblyFilePath();
  48. FileVersionInfo assemblyVersion = null;
  49. if (assemblyFilePath != null)
  50. {
  51. assemblyVersion = FileVersionInfo.GetVersionInfo(assemblyFilePath);
  52. }
  53. CheckVersion(ExecutablePath, assemblyVersion);
  54. string configSwitch;
  55. if (_session.DefaultConfigurationInternal)
  56. {
  57. configSwitch = "/ini=nul ";
  58. }
  59. else
  60. {
  61. if (!string.IsNullOrEmpty(_session.IniFilePathInternal))
  62. {
  63. configSwitch = string.Format(CultureInfo.InvariantCulture, "/ini=\"{0}\" ", _session.IniFilePathInternal);
  64. }
  65. else
  66. {
  67. configSwitch = "";
  68. }
  69. }
  70. string logSwitch = null;
  71. if (!string.IsNullOrEmpty(_session.SessionLogPath))
  72. {
  73. logSwitch = string.Format(CultureInfo.InvariantCulture, "/log=\"{0}\" ", LogPathEscape(_session.SessionLogPath));
  74. }
  75. string xmlLogSwitch;
  76. if (useXmlLog)
  77. {
  78. xmlLogSwitch = string.Format(CultureInfo.InvariantCulture, "/xmllog=\"{0}\" /xmlgroups /xmllogrequired ", LogPathEscape(_session.XmlLogPath));
  79. }
  80. else
  81. {
  82. xmlLogSwitch = "";
  83. }
  84. string logLevelSwitch = null;
  85. if (_session.DebugLogLevel != 0)
  86. {
  87. logLevelSwitch = string.Format(CultureInfo.InvariantCulture, "/loglevel={0} ", _session.DebugLogLevel);
  88. }
  89. string assemblyVersionStr =
  90. (assemblyVersion == null) ? "unk" :
  91. string.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2} ", assemblyVersion.ProductMajorPart, assemblyVersion.ProductMinorPart, assemblyVersion.ProductBuildPart);
  92. string assemblyVersionSwitch =
  93. string.Format(CultureInfo.InvariantCulture, "/dotnet={0} ", assemblyVersionStr);
  94. string arguments =
  95. xmlLogSwitch + "/nointeractiveinput /stdout /stdin " + assemblyVersionSwitch +
  96. configSwitch + logSwitch + logLevelSwitch + _session.AdditionalExecutableArguments;
  97. Tools.AddRawParameters(ref arguments, _session.RawConfiguration, "/rawconfig", false);
  98. if (!string.IsNullOrEmpty(additionalArguments))
  99. {
  100. arguments += " " + additionalArguments;
  101. }
  102. _process = new Process();
  103. _process.StartInfo.FileName = ExecutablePath;
  104. _process.StartInfo.WorkingDirectory = Path.GetDirectoryName(ExecutablePath);
  105. _process.StartInfo.Arguments = arguments;
  106. _process.StartInfo.UseShellExecute = false;
  107. _process.Exited += ProcessExited;
  108. }
  109. }
  110. private static string LogPathEscape(string path)
  111. {
  112. return Tools.ArgumentEscape(path).Replace("!", "!!");
  113. }
  114. public void Abort()
  115. {
  116. using (_logger.CreateCallstack())
  117. {
  118. lock (_lock)
  119. {
  120. if ((_process != null) && !_process.HasExited)
  121. {
  122. _process.Kill();
  123. }
  124. }
  125. }
  126. }
  127. public void Start()
  128. {
  129. using (_logger.CreateCallstack())
  130. {
  131. InitializeConsole();
  132. InitializeChild();
  133. }
  134. }
  135. private void InitializeChild()
  136. {
  137. using (_logger.CreateCallstack())
  138. {
  139. // The /console is redundant for CreateForConsole
  140. _process.StartInfo.Arguments += string.Format(CultureInfo.InvariantCulture, " /console /consoleinstance={0}", _instanceName);
  141. #if !NETSTANDARD
  142. // When running under IIS in "impersonated" mode, the process starts, but does not do anything.
  143. // Supposedly it "displayes" some invisible error message when starting and hangs.
  144. // Running it "as the user" helps, eventhough it already runs as the user.
  145. // These's probably some difference between "run as" and impersonations
  146. if (!string.IsNullOrEmpty(_session.ExecutableProcessUserName))
  147. {
  148. _logger.WriteLine("Will run process as {0}", _session.ExecutableProcessUserName);
  149. _process.StartInfo.UserName = _session.ExecutableProcessUserName;
  150. _process.StartInfo.Password = _session.ExecutableProcessPassword;
  151. // One of the hints for resolving C0000142 error (see below)
  152. // was setting this property, so that an environment is correctly loaded,
  153. // so DLLs can be found and loaded.
  154. _process.StartInfo.LoadUserProfile = true;
  155. // Without granting both window station and desktop access permissions,
  156. // WinSCP process aborts with C0000142 (DLL Initialization Failed) error,
  157. // when "running as user"
  158. _logger.WriteLine("Granting access to window station");
  159. try
  160. {
  161. IntPtr windowStation = UnsafeNativeMethods.GetProcessWindowStation();
  162. GrantAccess(windowStation, (int)WindowStationRights.AllAccess);
  163. }
  164. catch (Exception e)
  165. {
  166. throw _logger.WriteException(new SessionLocalException(_session, "Error granting access to window station", e));
  167. }
  168. _logger.WriteLine("Granting access to desktop");
  169. try
  170. {
  171. IntPtr desktop = UnsafeNativeMethods.GetThreadDesktop(UnsafeNativeMethods.GetCurrentThreadId());
  172. GrantAccess(desktop, (int)DesktopRights.AllAccess);
  173. }
  174. catch (Exception e)
  175. {
  176. throw _logger.WriteException(new SessionLocalException(_session, "Error granting access to desktop", e));
  177. }
  178. }
  179. #endif
  180. _logger.WriteLine("Starting \"{0}\" {1}", _process.StartInfo.FileName, _process.StartInfo.Arguments);
  181. _process.Start();
  182. _logger.WriteLine("Started process {0}", _process.Id);
  183. _thread = new Thread(ProcessEvents)
  184. {
  185. IsBackground = true
  186. };
  187. _thread.Start();
  188. }
  189. }
  190. // Handles returned by GetProcessWindowStation and GetThreadDesktop should not be closed
  191. internal class NoopSafeHandle : SafeHandle
  192. {
  193. public NoopSafeHandle(IntPtr handle) :
  194. base(handle, false)
  195. {
  196. }
  197. public override bool IsInvalid
  198. {
  199. get { return false; }
  200. }
  201. protected override bool ReleaseHandle()
  202. {
  203. return true;
  204. }
  205. }
  206. #if !NETSTANDARD
  207. private void GrantAccess(IntPtr handle, int accessMask)
  208. {
  209. using (SafeHandle safeHandle = new NoopSafeHandle(handle))
  210. {
  211. GenericSecurity security =
  212. new GenericSecurity(false, ResourceType.WindowObject, safeHandle, AccessControlSections.Access);
  213. security.AddAccessRule(
  214. new GenericAccessRule(new NTAccount(_session.ExecutableProcessUserName), accessMask, AccessControlType.Allow));
  215. security.Persist(safeHandle, AccessControlSections.Access);
  216. }
  217. }
  218. #endif
  219. private void ProcessExited(object sender, EventArgs e)
  220. {
  221. _logger.WriteLine("Process {0} exited with exit code {1}", _process.Id, _process.ExitCode);
  222. }
  223. private bool AbortedOrExited()
  224. {
  225. if (_abort)
  226. {
  227. _logger.WriteLine("Aborted");
  228. return true;
  229. }
  230. else if (_process.HasExited)
  231. {
  232. _logger.WriteLine("Exited");
  233. return true;
  234. }
  235. else
  236. {
  237. return false;
  238. }
  239. }
  240. private void ProcessEvents()
  241. {
  242. using (_logger.CreateCallstack())
  243. {
  244. try
  245. {
  246. while (!AbortedOrExited())
  247. {
  248. _logger.WriteLineLevel(1, "Waiting for request event");
  249. // Keep in sync with a delay in SessionLogReader.DoRead
  250. if (_requestEvent.WaitOne(100, false))
  251. {
  252. _logger.WriteLineLevel(1, "Got request event");
  253. ProcessEvent();
  254. }
  255. if (_logger.LogLevel >= 1)
  256. {
  257. _logger.WriteLine(string.Format(CultureInfo.InvariantCulture, "2nd generation collection count: {0}", GC.CollectionCount(2)));
  258. _logger.WriteLine(string.Format(CultureInfo.InvariantCulture, "Total memory allocated: {0}", GC.GetTotalMemory(false)));
  259. }
  260. }
  261. }
  262. catch (Exception e)
  263. {
  264. _logger.WriteLine("Error while processing events");
  265. _logger.WriteException(e);
  266. throw;
  267. }
  268. }
  269. }
  270. private void ProcessEvent()
  271. {
  272. using (_logger.CreateCallstack())
  273. {
  274. using (ConsoleCommStruct commStruct = AcquireCommStruct())
  275. {
  276. switch (commStruct.Event)
  277. {
  278. case ConsoleEvent.Print:
  279. ProcessPrintEvent(commStruct.PrintEvent);
  280. break;
  281. case ConsoleEvent.Input:
  282. ProcessInputEvent(commStruct.InputEvent);
  283. break;
  284. case ConsoleEvent.Choice:
  285. ProcessChoiceEvent(commStruct.ChoiceEvent);
  286. break;
  287. case ConsoleEvent.Title:
  288. ProcessTitleEvent(commStruct.TitleEvent);
  289. break;
  290. case ConsoleEvent.Init:
  291. ProcessInitEvent(commStruct.InitEvent);
  292. break;
  293. case ConsoleEvent.Progress:
  294. ProcessProgressEvent(commStruct.ProgressEvent);
  295. break;
  296. case ConsoleEvent.TransferOut:
  297. ProcessTransferOutEvent(commStruct.TransferOutEvent);
  298. break;
  299. case ConsoleEvent.TransferIn:
  300. ProcessTransferInEvent(commStruct.TransferInEvent);
  301. break;
  302. default:
  303. throw _logger.WriteException(new NotImplementedException());
  304. }
  305. }
  306. _responseEvent.Set();
  307. _logger.WriteLineLevel(1, "Response event set");
  308. }
  309. }
  310. private void ProcessChoiceEvent(ConsoleChoiceEventStruct e)
  311. {
  312. using (_logger.CreateCallstack())
  313. {
  314. _logger.WriteLine(
  315. "Options: [{0}], Timer: [{1}], Timeouting: [{2}], Timeouted: [{3}], Break: [{4}]",
  316. e.Options, e.Timer, e.Timeouting, e.Timeouted, e.Break);
  317. QueryReceivedEventArgs args = new QueryReceivedEventArgs
  318. {
  319. Message = e.Message
  320. };
  321. _session.ProcessChoice(args);
  322. if (args.SelectedAction == QueryReceivedEventArgs.Action.None)
  323. {
  324. if (e.Timeouting)
  325. {
  326. Thread.Sleep((int)e.Timer);
  327. e.Result = e.Timeouted;
  328. }
  329. else
  330. {
  331. e.Result = e.Break;
  332. }
  333. }
  334. else if (args.SelectedAction == QueryReceivedEventArgs.Action.Continue)
  335. {
  336. if (e.Timeouting)
  337. {
  338. Thread.Sleep((int)e.Timer);
  339. e.Result = e.Timeouted;
  340. }
  341. else
  342. {
  343. e.Result = e.Continue;
  344. }
  345. }
  346. else if (args.SelectedAction == QueryReceivedEventArgs.Action.Abort)
  347. {
  348. e.Result = e.Break;
  349. }
  350. _logger.WriteLine("Options Result: [{0}]", e.Result);
  351. }
  352. }
  353. private void ProcessTitleEvent(ConsoleTitleEventStruct e)
  354. {
  355. using (_logger.CreateCallstack())
  356. {
  357. _logger.WriteLine("Not-supported title event [{0}]", e.Title);
  358. }
  359. }
  360. private void ProcessInputEvent(ConsoleInputEventStruct e)
  361. {
  362. using (_logger.CreateCallstack())
  363. {
  364. while (!AbortedOrExited())
  365. {
  366. lock (_input)
  367. {
  368. if (_input.Count > 0)
  369. {
  370. e.Str = _input[0];
  371. e.Result = true;
  372. _input.RemoveAt(0);
  373. Print(false, false, _log[0] + "\n");
  374. _log.RemoveAt(0);
  375. return;
  376. }
  377. }
  378. _inputEvent.WaitOne(100, false);
  379. }
  380. }
  381. }
  382. private void Print(bool fromBeginning, bool error, string message)
  383. {
  384. if (fromBeginning && ((message.Length == 0) || (message[0] != '\n')))
  385. {
  386. _lastFromBeginning = message;
  387. _logger.WriteLine("Buffered from-beginning message [{0}]", _lastFromBeginning);
  388. OutputDataReceived?.Invoke(this, null);
  389. }
  390. else
  391. {
  392. if (!string.IsNullOrEmpty(_lastFromBeginning))
  393. {
  394. AddToOutput(_lastFromBeginning, false);
  395. _lastFromBeginning = null;
  396. }
  397. if (fromBeginning && (message.Length > 0) && (message[0] == '\n'))
  398. {
  399. AddToOutput("\n", false);
  400. _lastFromBeginning = message.Substring(1);
  401. _logger.WriteLine("Buffered from-beginning message [{0}]", _lastFromBeginning);
  402. }
  403. else
  404. {
  405. AddToOutput(message, error);
  406. }
  407. }
  408. }
  409. private void AddToOutput(string message, bool error)
  410. {
  411. string[] lines = (_incompleteLine + message).Split(new[] { '\n' });
  412. _incompleteLine = lines[lines.Length - 1];
  413. for (int i = 0; i < lines.Length - 1; ++i)
  414. {
  415. OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs(lines[i], error));
  416. }
  417. }
  418. private void ProcessPrintEvent(ConsolePrintEventStruct e)
  419. {
  420. _logger.WriteLineLevel(1, string.Format(CultureInfo.CurrentCulture, "Print: {0}", e.Message));
  421. Print(e.FromBeginning, e.Error, e.Message);
  422. }
  423. private void ProcessInitEvent(ConsoleInitEventStruct e)
  424. {
  425. using (_logger.CreateCallstack())
  426. {
  427. if (!e.UseStdErr ||
  428. (e.BinaryOutput != ConsoleInitEventStruct.StdInOut.Binary) ||
  429. (e.BinaryInput != ConsoleInitEventStruct.StdInOut.Binary))
  430. {
  431. throw _logger.WriteException(new InvalidOperationException("Unexpected console interface options"));
  432. }
  433. e.InputType = 3; // pipe
  434. e.OutputType = 3; // pipe
  435. e.WantsProgress = _session.WantsProgress;
  436. }
  437. }
  438. private void ProcessProgressEvent(ConsoleProgressEventStruct e)
  439. {
  440. using (_logger.CreateCallstack())
  441. {
  442. _logger.WriteLine(
  443. "File Name [{0}] - Directory [{1}] - Overall Progress [{2}] - File Progress [{3}] - CPS [{4}]",
  444. e.FileName, e.Directory, e.OverallProgress, e.FileProgress, e.CPS);
  445. if (!_cancel)
  446. {
  447. FileTransferProgressEventArgs args = new FileTransferProgressEventArgs();
  448. switch (e.Operation)
  449. {
  450. case ConsoleProgressEventStruct.ProgressOperation.Copy:
  451. args.Operation = ProgressOperation.Transfer;
  452. break;
  453. default:
  454. throw _logger.WriteException(new ArgumentOutOfRangeException("Unknown progress operation", (Exception)null));
  455. }
  456. switch (e.Side)
  457. {
  458. case ConsoleProgressEventStruct.ProgressSide.Local:
  459. args.Side = ProgressSide.Local;
  460. break;
  461. case ConsoleProgressEventStruct.ProgressSide.Remote:
  462. args.Side = ProgressSide.Remote;
  463. break;
  464. default:
  465. throw _logger.WriteException(new ArgumentOutOfRangeException("Unknown progress side", (Exception)null));
  466. }
  467. args.FileName = e.FileName;
  468. args.Directory = e.Directory;
  469. args.OverallProgress = ((double)e.OverallProgress) / 100;
  470. args.FileProgress = ((double)e.FileProgress) / 100;
  471. args.CPS = (int)e.CPS;
  472. args.Cancel = false;
  473. _session.ProcessProgress(args);
  474. }
  475. if (_cancel)
  476. {
  477. e.Cancel = true;
  478. }
  479. }
  480. }
  481. private void ProcessTransferOutEvent(ConsoleTransferEventStruct e)
  482. {
  483. using (_logger.CreateCallstack())
  484. {
  485. _logger.WriteLine("Len [{0}]", e.Len);
  486. if (StdOut == null)
  487. {
  488. throw _logger.WriteException(new InvalidOperationException("Unexpected data"));
  489. }
  490. int len = (int)e.Len;
  491. if (len > 0)
  492. {
  493. StdOut.WriteInternal(e.Data, 0, len);
  494. _logger.WriteLine("Data written to the buffer");
  495. }
  496. else
  497. {
  498. StdOut.CloseWrite();
  499. _logger.WriteLine("Data buffer closed");
  500. }
  501. }
  502. }
  503. private void ProcessTransferInEvent(ConsoleTransferEventStruct e)
  504. {
  505. using (_logger.CreateCallstack())
  506. {
  507. _logger.WriteLine("Len [{0}]", e.Len);
  508. if (StdIn == null)
  509. {
  510. throw _logger.WriteException(new InvalidOperationException("Unexpected data request"));
  511. }
  512. try
  513. {
  514. int len = (int)e.Len;
  515. len = StdIn.Read(e.Data, 0, len);
  516. _logger.WriteLine("{0} bytes read", len);
  517. e.Len = (uint)len;
  518. }
  519. catch (Exception ex)
  520. {
  521. _logger.WriteLine("Error reading data stream");
  522. _logger.WriteException(ex);
  523. e.Error = true;
  524. }
  525. }
  526. }
  527. private void InitializeConsole()
  528. {
  529. using (_logger.CreateCallstack())
  530. {
  531. int attempts = 0;
  532. Random random = new Random();
  533. int process = Process.GetCurrentProcess().Id;
  534. do
  535. {
  536. if (attempts > MaxAttempts)
  537. {
  538. throw _logger.WriteException(new SessionLocalException(_session, "Cannot find unique name for event object."));
  539. }
  540. int instanceNumber = random.Next(1000);
  541. _instanceName = string.Format(CultureInfo.InvariantCulture, "_{0}_{1}_{2}", process, GetHashCode(), instanceNumber);
  542. _logger.WriteLine("Trying event {0}", _instanceName);
  543. if (!TryCreateEvent(ConsoleEventRequest + _instanceName, out _requestEvent))
  544. {
  545. _logger.WriteLine("Event {0} is not unique", _instanceName);
  546. _requestEvent.Close();
  547. _requestEvent = null;
  548. }
  549. else
  550. {
  551. _logger.WriteLine("Event {0} is unique", _instanceName);
  552. _responseEvent = CreateEvent(ConsoleEventResponse + _instanceName);
  553. _cancelEvent = CreateEvent(ConsoleEventCancel + _instanceName);
  554. string fileMappingName = ConsoleMapping + _instanceName;
  555. _fileMapping = CreateFileMapping(fileMappingName);
  556. if (Marshal.GetLastWin32Error() == UnsafeNativeMethods.ERROR_ALREADY_EXISTS)
  557. {
  558. throw _logger.WriteException(new SessionLocalException(_session, string.Format(CultureInfo.InvariantCulture, "File mapping {0} already exists", fileMappingName)));
  559. }
  560. if (_fileMapping.IsInvalid)
  561. {
  562. throw _logger.WriteException(new SessionLocalException(_session, string.Format(CultureInfo.InvariantCulture, "Cannot create file mapping {0}", fileMappingName)));
  563. }
  564. }
  565. ++attempts;
  566. }
  567. while (_requestEvent == null);
  568. using (ConsoleCommStruct commStruct = AcquireCommStruct())
  569. {
  570. commStruct.InitHeader();
  571. }
  572. if (_session.GuardProcessWithJobInternal)
  573. {
  574. string jobName = ConsoleJob + _instanceName;
  575. _job = new Job(_logger, jobName);
  576. }
  577. }
  578. }
  579. private SafeFileHandle CreateFileMapping(string fileMappingName)
  580. {
  581. unsafe
  582. {
  583. IntPtr securityAttributesPtr = IntPtr.Zero;
  584. #if !NETSTANDARD
  585. // We use the EventWaitHandleSecurity only to generate the descriptor binary form
  586. // that does not differ for object types, so we abuse the existing "event handle" implementation,
  587. // not to have to create the file mapping SecurityAttributes via P/Invoke.
  588. // .NET 4 supports MemoryMappedFile and MemoryMappedFileSecurity natively already
  589. EventWaitHandleSecurity security = CreateSecurity((EventWaitHandleRights)FileMappingRights.AllAccess);
  590. if (security != null)
  591. {
  592. SecurityAttributes securityAttributes = new SecurityAttributes();
  593. securityAttributes.nLength = (uint)Marshal.SizeOf(securityAttributes);
  594. byte[] descriptorBinaryForm = security.GetSecurityDescriptorBinaryForm();
  595. byte * buffer = stackalloc byte[descriptorBinaryForm.Length];
  596. for (int i = 0; i < descriptorBinaryForm.Length; i++)
  597. {
  598. buffer[i] = descriptorBinaryForm[i];
  599. }
  600. securityAttributes.lpSecurityDescriptor = (IntPtr)buffer;
  601. int length = Marshal.SizeOf(typeof(SecurityAttributes));
  602. securityAttributesPtr = Marshal.AllocHGlobal(length);
  603. Marshal.StructureToPtr(securityAttributes, securityAttributesPtr, false);
  604. }
  605. #endif
  606. return
  607. UnsafeNativeMethods.CreateFileMapping(
  608. new SafeFileHandle(new IntPtr(-1), true), securityAttributesPtr, FileMapProtection.PageReadWrite, 0,
  609. ConsoleCommStruct.Size, fileMappingName);
  610. }
  611. }
  612. private ConsoleCommStruct AcquireCommStruct()
  613. {
  614. return new ConsoleCommStruct(_session, _fileMapping);
  615. }
  616. private bool TryCreateEvent(string name, out EventWaitHandle ev)
  617. {
  618. _logger.WriteLine("Creating event {0}", name);
  619. string securityDesc;
  620. #if !NETSTANDARD
  621. EventWaitHandleSecurity security = CreateSecurity(EventWaitHandleRights.FullControl);
  622. ev = new EventWaitHandle(false, EventResetMode.AutoReset, name, out bool createdNew, security);
  623. securityDesc = (security != null ? security.GetSecurityDescriptorSddlForm(AccessControlSections.All) : "none");
  624. #else
  625. ev = new EventWaitHandle(false, EventResetMode.AutoReset, name, out bool createdNew);
  626. securityDesc = "not impl";
  627. #endif
  628. _logger.WriteLine(
  629. "Created event {0} with handle {1} with security {2}, new {3}",
  630. name, ev.SafeWaitHandle.DangerousGetHandle(), securityDesc, createdNew);
  631. return createdNew;
  632. }
  633. #if !NETSTANDARD
  634. private EventWaitHandleSecurity CreateSecurity(EventWaitHandleRights eventRights)
  635. {
  636. EventWaitHandleSecurity security = null;
  637. // When "running as user", we have to grant the target user permissions to the objects (events and file mapping) explicitly
  638. if (!string.IsNullOrEmpty(_session.ExecutableProcessUserName))
  639. {
  640. security = new EventWaitHandleSecurity();
  641. IdentityReference si;
  642. try
  643. {
  644. si = new NTAccount(_session.ExecutableProcessUserName);
  645. }
  646. catch (Exception e)
  647. {
  648. throw _logger.WriteException(new SessionLocalException(_session, string.Format(CultureInfo.CurrentCulture, "Error resolving account {0}", _session.ExecutableProcessUserName), e));
  649. }
  650. EventWaitHandleAccessRule rule =
  651. new EventWaitHandleAccessRule(
  652. si, eventRights, AccessControlType.Allow);
  653. security.AddAccessRule(rule);
  654. }
  655. return security;
  656. }
  657. #endif
  658. private EventWaitHandle CreateEvent(string name)
  659. {
  660. if (!TryCreateEvent(name, out EventWaitHandle ev))
  661. {
  662. throw _logger.WriteException(new SessionLocalException(_session, string.Format(CultureInfo.InvariantCulture, "Event {0} already exists", name)));
  663. }
  664. return ev;
  665. }
  666. private void TestEventClosed(string name)
  667. {
  668. if (_session.TestHandlesClosedInternal)
  669. {
  670. _logger.WriteLine("Testing that event {0} is closed", name);
  671. if (TryCreateEvent(name, out EventWaitHandle ev))
  672. {
  673. ev.Close();
  674. }
  675. else
  676. {
  677. _logger.WriteLine("Exception: Event {0} was not closed yet", name);
  678. }
  679. }
  680. }
  681. private void AddInput(string str, string log)
  682. {
  683. Type structType = typeof(ConsoleInputEventStruct);
  684. FieldInfo strField = structType.GetField("Str");
  685. object[] attributes = strField.GetCustomAttributes(typeof(MarshalAsAttribute), false);
  686. if (attributes.Length != 1)
  687. {
  688. throw _logger.WriteException(new InvalidOperationException("MarshalAs attribute not found for ConsoleInputEventStruct.Str"));
  689. }
  690. MarshalAsAttribute marshalAsAttribute = (MarshalAsAttribute)attributes[0];
  691. if (marshalAsAttribute.SizeConst <= str.Length)
  692. {
  693. throw _logger.WriteException(
  694. new SessionLocalException(
  695. _session,
  696. string.Format(CultureInfo.CurrentCulture, "Input [{0}] is too long ({1} limit)", str, marshalAsAttribute.SizeConst)));
  697. }
  698. lock (_input)
  699. {
  700. _input.Add(str);
  701. _log.Add(log);
  702. _inputEvent.Set();
  703. }
  704. }
  705. public void ExecuteCommand(string command, string log)
  706. {
  707. using (_logger.CreateCallstack())
  708. {
  709. _cancel = false;
  710. AddInput(command, log);
  711. }
  712. }
  713. public void Close()
  714. {
  715. using (_logger.CreateCallstack())
  716. {
  717. int timeout;
  718. #if DEBUG
  719. // in debug build, we expect the winscp.exe to run in tracing mode, being very slow
  720. timeout = 10000;
  721. #else
  722. timeout = 2000;
  723. #endif
  724. _logger.WriteLine("Waiting for process to exit ({0} ms)", timeout);
  725. if (!_process.WaitForExit(timeout))
  726. {
  727. _logger.WriteLine("Killing process");
  728. _process.Kill();
  729. }
  730. }
  731. }
  732. public void Dispose()
  733. {
  734. using (_logger.CreateCallstack())
  735. {
  736. lock (_lock)
  737. {
  738. if (_session.TestHandlesClosedInternal)
  739. {
  740. _logger.WriteLine("Will test that handles are closed");
  741. }
  742. _abort = true;
  743. if (_thread != null)
  744. {
  745. _thread.Join();
  746. _thread = null;
  747. }
  748. if (_process != null)
  749. {
  750. _process.Dispose();
  751. _process = null;
  752. }
  753. if (_requestEvent != null)
  754. {
  755. _requestEvent.Close();
  756. TestEventClosed(ConsoleEventRequest + _instanceName);
  757. }
  758. if (_responseEvent != null)
  759. {
  760. _responseEvent.Close();
  761. TestEventClosed(ConsoleEventResponse + _instanceName);
  762. }
  763. if (_cancelEvent != null)
  764. {
  765. _cancelEvent.Close();
  766. TestEventClosed(ConsoleEventCancel + _instanceName);
  767. }
  768. if (_fileMapping != null)
  769. {
  770. _fileMapping.Dispose();
  771. _fileMapping = null;
  772. if (_session.TestHandlesClosedInternal)
  773. {
  774. _logger.WriteLine("Testing that file mapping is closed");
  775. string fileMappingName = ConsoleMapping + _instanceName;
  776. SafeFileHandle fileMapping = CreateFileMapping(fileMappingName);
  777. if (Marshal.GetLastWin32Error() == UnsafeNativeMethods.ERROR_ALREADY_EXISTS)
  778. {
  779. _logger.WriteLine("Exception: File mapping {0} was not closed yet", fileMappingName);
  780. }
  781. if (!fileMapping.IsInvalid)
  782. {
  783. fileMapping.Dispose();
  784. }
  785. }
  786. }
  787. if (_inputEvent != null)
  788. {
  789. _inputEvent.Close();
  790. _inputEvent = null;
  791. }
  792. if (_job != null)
  793. {
  794. _job.Dispose();
  795. _job = null;
  796. }
  797. }
  798. }
  799. }
  800. private string GetExecutablePath()
  801. {
  802. using (_logger.CreateCallstack())
  803. {
  804. string executablePath = _session.ExecutablePath;
  805. string result;
  806. if (!string.IsNullOrEmpty(executablePath))
  807. {
  808. if (!File.Exists(executablePath))
  809. {
  810. throw _logger.WriteException(new SessionLocalException(_session, $"{executablePath} does not exists."));
  811. }
  812. result = executablePath;
  813. }
  814. else
  815. {
  816. result = FindExecutable(_session);
  817. }
  818. return result;
  819. }
  820. }
  821. // Should be moved to Session class
  822. internal static string FindExecutable(Session session)
  823. {
  824. Logger logger = session.Logger;
  825. string executablePath;
  826. List<string> paths = [];
  827. string assemblyPath = GetAssemblyPath(logger);
  828. // If the assembly is not loaded from a file, look to the path of the process execuable
  829. // (particularly useful for single-file bundles)
  830. // (also limited this way not to for example look into powershell.exe folder)
  831. string processPath = !string.IsNullOrEmpty(assemblyPath) ? null : Path.GetDirectoryName(Logger.GetProcessPath());
  832. if (!TryFindExecutableInPath(logger, paths, assemblyPath, out executablePath) &&
  833. !TryFindExecutableInPath(logger, paths, GetEntryAssemblyPath(logger), out executablePath) &&
  834. !TryFindExecutableInPath(logger, paths, processPath, out executablePath) &&
  835. #if !NETSTANDARD
  836. !TryFindExecutableInPath(logger, paths, GetInstallationPath(RegistryHive.CurrentUser), out executablePath) &&
  837. !TryFindExecutableInPath(logger, paths, GetInstallationPath(RegistryHive.LocalMachine), out executablePath) &&
  838. #endif
  839. !TryFindExecutableInPath(logger, paths, GetDefaultInstallationPath(), out executablePath))
  840. {
  841. string pathsStr = string.Join(", ", paths);
  842. string filename = ExeExecutableFileName;
  843. string message =
  844. $"The {filename} executable was not found at any of the inspected locations ({pathsStr}). " +
  845. $"You may use Session.ExecutablePath property to explicitly set path to {filename}.";
  846. throw logger.WriteException(new SessionLocalException(session, message));
  847. }
  848. return executablePath;
  849. }
  850. private static string GetDefaultInstallationPath()
  851. {
  852. string programFiles;
  853. if (IntPtr.Size == 8)
  854. {
  855. programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
  856. }
  857. else
  858. {
  859. programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
  860. }
  861. return Path.Combine(programFiles, "WinSCP");
  862. }
  863. #if !NETSTANDARD
  864. private static string GetInstallationPath(RegistryHive hive)
  865. {
  866. RegistryKey baseKey = RegistryKey.OpenBaseKey(hive, RegistryView.Registry32);
  867. RegistryKey key = baseKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\winscp3_is1");
  868. string result = (key != null) ? (string)key.GetValue("Inno Setup: App Path") : null;
  869. return result;
  870. }
  871. #endif
  872. private static bool TryFindExecutableInPath(Logger logger, List<string> paths, string path, out string result)
  873. {
  874. if (string.IsNullOrEmpty(path))
  875. {
  876. result = null;
  877. }
  878. else if (paths.Contains(path, StringComparer.CurrentCultureIgnoreCase))
  879. {
  880. logger.WriteLine($"Already searched {path}");
  881. result = null;
  882. }
  883. else
  884. {
  885. paths.Add(path);
  886. string executablePath = Path.Combine(path, ExeExecutableFileName);
  887. if (File.Exists(executablePath))
  888. {
  889. result = executablePath;
  890. logger.WriteLine("Executable found in {0}", executablePath);
  891. }
  892. else
  893. {
  894. result = null;
  895. logger.WriteLine("Executable not found in {0}", executablePath);
  896. }
  897. }
  898. return (result != null);
  899. }
  900. private static string GetAssemblyPath(Logger logger)
  901. {
  902. return DoGetAssemblyPath(logger.GetAssemblyFilePath());
  903. }
  904. private static string GetEntryAssemblyPath(Logger logger)
  905. {
  906. return DoGetAssemblyPath(logger.GetEntryAssemblyFilePath());
  907. }
  908. private static string DoGetAssemblyPath(string codeBasePath)
  909. {
  910. string path = null;
  911. if (!string.IsNullOrEmpty(codeBasePath))
  912. {
  913. path = Path.GetDirectoryName(codeBasePath);
  914. Debug.Assert(path != null);
  915. }
  916. return path;
  917. }
  918. [DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
  919. public static extern int GetFileVersionInfoSize(string lptstrFilename, out int handle);
  920. [DllImport("kernel32.dll", SetLastError = true)]
  921. static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, uint dwFlags);
  922. [DllImport("kernel32.dll", SetLastError = true)]
  923. [return: MarshalAs(UnmanagedType.Bool)]
  924. static extern bool FreeLibrary(IntPtr hModule);
  925. [DllImport("kernel32.dll", SetLastError = true)]
  926. static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
  927. [DllImport("kernel32.dll", SetLastError = true)]
  928. static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);
  929. private string GetVersionStr(FileVersionInfo version)
  930. {
  931. return $"{version.FileVersion}, product {version.ProductName} version is {version.ProductVersion}";
  932. }
  933. private void CheckVersion(string exePath, FileVersionInfo assemblyVersion)
  934. {
  935. using (_logger.CreateCallstack())
  936. {
  937. if (assemblyVersion == null)
  938. {
  939. _logger.WriteLine("Assembly version not known, cannot check version");
  940. }
  941. else if (assemblyVersion.ProductVersion == AssemblyConstants.UndefinedProductVersion)
  942. {
  943. _logger.WriteLine("Undefined assembly version, cannot check version");
  944. }
  945. else
  946. {
  947. DateTime dateTime = File.GetLastWriteTimeUtc(exePath);
  948. _logger.WriteLine($"Timestamp of {exePath} is {dateTime}");
  949. bool known;
  950. var cacheKey = new Tuple<string, DateTime>(exePath, dateTime);
  951. lock (_versionInfoCache)
  952. {
  953. known = _versionInfoCache.TryGetValue(cacheKey, out FileVersionInfo version);
  954. if (known)
  955. {
  956. _logger.WriteLine(
  957. $"Cached version of {exePath} is {GetVersionStr(version)}, and it was already deemed compatible");
  958. }
  959. else
  960. {
  961. _logger.WriteLine($"Executable version is not cached yet, cache size is {_versionInfoCache.Count}");
  962. }
  963. }
  964. if (!known)
  965. {
  966. FileVersionInfo version = FileVersionInfo.GetVersionInfo(exePath);
  967. _logger.WriteLine($"Version of {exePath} is {GetVersionStr(version)}");
  968. bool incompatible = assemblyVersion.ProductVersion != version.ProductVersion;
  969. Exception accessException = null;
  970. if (incompatible || _logger.Logging)
  971. {
  972. try
  973. {
  974. using (File.OpenRead(exePath))
  975. {
  976. }
  977. long size = new FileInfo(exePath).Length;
  978. _logger.WriteLine($"Size of the executable file is {size}");
  979. int verInfoSize = GetFileVersionInfoSize(exePath, out int handle);
  980. if (verInfoSize == 0)
  981. {
  982. throw new Exception($"Cannot retrieve {exePath} version info", new Win32Exception());
  983. }
  984. else
  985. {
  986. _logger.WriteLine($"Size of the executable file version info is {verInfoSize}");
  987. }
  988. }
  989. catch (Exception e)
  990. {
  991. _logger.WriteLine("Accessing executable file failed");
  992. _logger.WriteException(e);
  993. accessException = e;
  994. }
  995. }
  996. if (_session.DisableVersionCheck)
  997. {
  998. _logger.WriteLine("Version check disabled (not recommended)");
  999. }
  1000. else if (incompatible)
  1001. {
  1002. if (_logger.Logging)
  1003. {
  1004. try
  1005. {
  1006. using (SHA256 SHA256 = SHA256.Create())
  1007. using (FileStream stream = File.OpenRead(exePath))
  1008. {
  1009. string sha256 = string.Concat(Array.ConvertAll(SHA256.ComputeHash(stream), b => b.ToString("x2")));
  1010. _logger.WriteLine($"SHA-256 of the executable file is {sha256}");
  1011. }
  1012. }
  1013. catch (Exception e)
  1014. {
  1015. _logger.WriteLine("Calculating SHA-256 of the executable file failed");
  1016. _logger.WriteException(e);
  1017. }
  1018. try
  1019. {
  1020. IntPtr library = LoadLibraryEx(exePath, IntPtr.Zero, 0x00000002); // LOAD_LIBRARY_AS_DATAFILE
  1021. if (library == IntPtr.Zero)
  1022. {
  1023. _logger.WriteLine("Cannot load");
  1024. _logger.WriteException(new Win32Exception());
  1025. }
  1026. else
  1027. {
  1028. IntPtr resource = FindResource(library, "#1", "#16");
  1029. if (resource == IntPtr.Zero)
  1030. {
  1031. _logger.WriteLine("Cannot find version resource");
  1032. _logger.WriteException(new Win32Exception());
  1033. }
  1034. else
  1035. {
  1036. uint resourceSize = SizeofResource(library, resource);
  1037. if (resourceSize == 0)
  1038. {
  1039. _logger.WriteLine("Cannot find size of version resource");
  1040. _logger.WriteException(new Win32Exception());
  1041. }
  1042. else
  1043. {
  1044. _logger.WriteLine($"Version resource size is {resourceSize}");
  1045. }
  1046. }
  1047. FreeLibrary(library);
  1048. }
  1049. }
  1050. catch (Exception e)
  1051. {
  1052. _logger.WriteLine("Querying version resource failed");
  1053. _logger.WriteException(e);
  1054. }
  1055. }
  1056. string message;
  1057. if (string.IsNullOrEmpty(version.ProductVersion) && (accessException != null))
  1058. {
  1059. message = $"Cannot use {exePath}";
  1060. }
  1061. else
  1062. {
  1063. message =
  1064. $"The version of {exePath} ({version.ProductVersion}) does not match " +
  1065. $"version of this assembly {_logger.GetAssemblyFilePath()} ({assemblyVersion.ProductVersion}).";
  1066. }
  1067. throw _logger.WriteException(new SessionLocalException(_session, message, accessException));
  1068. }
  1069. else
  1070. {
  1071. lock (_versionInfoCache)
  1072. {
  1073. _logger.WriteLine("Caching executable version");
  1074. _versionInfoCache[cacheKey] = version;
  1075. }
  1076. }
  1077. }
  1078. }
  1079. }
  1080. }
  1081. public void WriteStatus()
  1082. {
  1083. _logger.WriteLine("{0} - exists [{1}]", ExecutablePath, File.Exists(ExecutablePath));
  1084. }
  1085. public void RequestCallstack()
  1086. {
  1087. using (_logger.CreateCallstack())
  1088. {
  1089. lock (_lock)
  1090. {
  1091. if (_process == null)
  1092. {
  1093. _logger.WriteLine("Process is closed already");
  1094. }
  1095. else
  1096. {
  1097. try
  1098. {
  1099. string eventName = string.Format(CultureInfo.InvariantCulture, "WinSCPCallstack{0}", _process.Id);
  1100. using (EventWaitHandle ev = EventWaitHandle.OpenExisting(eventName))
  1101. {
  1102. _logger.WriteLine("Setting event {0}", eventName);
  1103. ev.Set();
  1104. string callstackFileName = string.Format(CultureInfo.InvariantCulture, "{0}.txt", eventName);
  1105. string callstackPath = Path.Combine(Path.GetTempPath(), callstackFileName);
  1106. int timeout = 2000;
  1107. while (!File.Exists(callstackPath))
  1108. {
  1109. if (timeout < 0)
  1110. {
  1111. string message = string.Format(CultureInfo.CurrentCulture, "Timeout waiting for callstack file {0} to be created ", callstackPath);
  1112. throw new TimeoutException(message);
  1113. }
  1114. int step = 50;
  1115. timeout -= 50;
  1116. Thread.Sleep(step);
  1117. }
  1118. _logger.WriteLine("Callstack file {0} has been created", callstackPath);
  1119. // allow writting to be finished
  1120. Thread.Sleep(100);
  1121. _logger.WriteLine(File.ReadAllText(callstackPath));
  1122. File.Delete(callstackPath);
  1123. }
  1124. }
  1125. catch (Exception e)
  1126. {
  1127. _logger.WriteException(e);
  1128. }
  1129. }
  1130. }
  1131. }
  1132. }
  1133. public void Cancel()
  1134. {
  1135. _cancel = true;
  1136. }
  1137. private const int MaxAttempts = 10;
  1138. private const string ConsoleMapping = "WinSCPConsoleMapping";
  1139. private const string ConsoleEventRequest = "WinSCPConsoleEventRequest";
  1140. private const string ConsoleEventResponse = "WinSCPConsoleEventResponse";
  1141. private const string ConsoleEventCancel = "WinSCPConsoleEventCancel";
  1142. private const string ConsoleJob = "WinSCPConsoleJob";
  1143. private const string ExeExecutableFileName = "winscp.exe";
  1144. private Process _process;
  1145. private readonly object _lock = new object();
  1146. private readonly Logger _logger;
  1147. private readonly Session _session;
  1148. private EventWaitHandle _requestEvent;
  1149. private EventWaitHandle _responseEvent;
  1150. private EventWaitHandle _cancelEvent;
  1151. private SafeFileHandle _fileMapping;
  1152. private string _instanceName;
  1153. private Thread _thread;
  1154. private bool _abort;
  1155. private string _lastFromBeginning;
  1156. private string _incompleteLine;
  1157. private readonly List<string> _input = new List<string>();
  1158. private readonly List<string> _log = new List<string>();
  1159. private AutoResetEvent _inputEvent = new AutoResetEvent(false);
  1160. private Job _job;
  1161. private bool _cancel;
  1162. private static readonly Dictionary<Tuple<string, DateTime>, FileVersionInfo> _versionInfoCache = new Dictionary<Tuple<string, DateTime>, FileVersionInfo>();
  1163. }
  1164. }