ExeSessionProcess.cs 43 KB

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