123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Globalization;
- using System.IO;
- using System.Threading;
- #if !NETSTANDARD
- using Microsoft.Win32;
- #endif
- using Microsoft.Win32.SafeHandles;
- using System.Runtime.InteropServices;
- using System.Reflection;
- #if !NETSTANDARD
- using System.Security.Principal;
- using System.Security.AccessControl;
- #endif
- using System.ComponentModel;
- using System.Security.Cryptography;
- using System.Linq;
- namespace WinSCP
- {
- internal class ExeSessionProcess : IDisposable
- {
- public event OutputDataReceivedEventHandler OutputDataReceived;
- public bool HasExited { get { return _process.HasExited; } }
- public int ExitCode { get { return _process.ExitCode; } }
- public PipeStream StdOut { get; set; }
- public Stream StdIn { get; set; }
- public string ExecutablePath { get; }
- public static ExeSessionProcess CreateForSession(Session session)
- {
- return new ExeSessionProcess(session, true, null);
- }
- public static ExeSessionProcess CreateForConsole(Session session, string additionalArguments)
- {
- return new ExeSessionProcess(session, false, additionalArguments);
- }
- private ExeSessionProcess(Session session, bool useXmlLog, string additionalArguments)
- {
- _session = session;
- _logger = session.Logger;
- _incompleteLine = string.Empty;
- using (_logger.CreateCallstack())
- {
- ExecutablePath = GetExecutablePath();
- _logger.WriteLine("EXE executable path resolved to {0}", ExecutablePath);
- string assemblyFilePath = _logger.GetAssemblyFilePath();
- FileVersionInfo assemblyVersion = null;
- if (assemblyFilePath != null)
- {
- assemblyVersion = FileVersionInfo.GetVersionInfo(assemblyFilePath);
- }
- CheckVersion(ExecutablePath, assemblyVersion);
- string configSwitch;
- if (_session.DefaultConfigurationInternal)
- {
- configSwitch = "/ini=nul ";
- }
- else
- {
- if (!string.IsNullOrEmpty(_session.IniFilePathInternal))
- {
- configSwitch = string.Format(CultureInfo.InvariantCulture, "/ini=\"{0}\" ", _session.IniFilePathInternal);
- }
- else
- {
- configSwitch = "";
- }
- }
- string logSwitch = null;
- if (!string.IsNullOrEmpty(_session.SessionLogPath))
- {
- logSwitch = string.Format(CultureInfo.InvariantCulture, "/log=\"{0}\" ", LogPathEscape(_session.SessionLogPath));
- }
- string xmlLogSwitch;
- if (useXmlLog)
- {
- xmlLogSwitch = string.Format(CultureInfo.InvariantCulture, "/xmllog=\"{0}\" /xmlgroups /xmllogrequired ", LogPathEscape(_session.XmlLogPath));
- }
- else
- {
- xmlLogSwitch = "";
- }
- string logLevelSwitch = null;
- if (_session.DebugLogLevel != 0)
- {
- logLevelSwitch = string.Format(CultureInfo.InvariantCulture, "/loglevel={0} ", _session.DebugLogLevel);
- }
- string assemblyVersionStr =
- (assemblyVersion == null) ? "unk" :
- string.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2} ", assemblyVersion.ProductMajorPart, assemblyVersion.ProductMinorPart, assemblyVersion.ProductBuildPart);
- string assemblyVersionSwitch =
- string.Format(CultureInfo.InvariantCulture, "/dotnet={0} ", assemblyVersionStr);
- string arguments =
- xmlLogSwitch + "/nointeractiveinput /stdout /stdin " + assemblyVersionSwitch +
- configSwitch + logSwitch + logLevelSwitch + _session.AdditionalExecutableArguments;
- Tools.AddRawParameters(ref arguments, _session.RawConfiguration, "/rawconfig", false);
- if (!string.IsNullOrEmpty(additionalArguments))
- {
- arguments += " " + additionalArguments;
- }
- _process = new Process();
- _process.StartInfo.FileName = ExecutablePath;
- _process.StartInfo.WorkingDirectory = Path.GetDirectoryName(ExecutablePath);
- _process.StartInfo.Arguments = arguments;
- _process.StartInfo.UseShellExecute = false;
- _process.Exited += ProcessExited;
- }
- }
- private static string LogPathEscape(string path)
- {
- return Tools.ArgumentEscape(path).Replace("!", "!!");
- }
- public void Abort()
- {
- using (_logger.CreateCallstack())
- {
- lock (_lock)
- {
- if ((_process != null) && !_process.HasExited)
- {
- _process.Kill();
- }
- }
- }
- }
- public void Start()
- {
- using (_logger.CreateCallstack())
- {
- InitializeConsole();
- InitializeChild();
- }
- }
- private void InitializeChild()
- {
- using (_logger.CreateCallstack())
- {
- // The /console is redundant for CreateForConsole
- _process.StartInfo.Arguments += string.Format(CultureInfo.InvariantCulture, " /console /consoleinstance={0}", _instanceName);
- #if !NETSTANDARD
- // When running under IIS in "impersonated" mode, the process starts, but does not do anything.
- // Supposedly it "displayes" some invisible error message when starting and hangs.
- // Running it "as the user" helps, eventhough it already runs as the user.
- // These's probably some difference between "run as" and impersonations
- if (!string.IsNullOrEmpty(_session.ExecutableProcessUserName))
- {
- _logger.WriteLine("Will run process as {0}", _session.ExecutableProcessUserName);
- _process.StartInfo.UserName = _session.ExecutableProcessUserName;
- _process.StartInfo.Password = _session.ExecutableProcessPassword;
- // One of the hints for resolving C0000142 error (see below)
- // was setting this property, so that an environment is correctly loaded,
- // so DLLs can be found and loaded.
- _process.StartInfo.LoadUserProfile = true;
- // Without granting both window station and desktop access permissions,
- // WinSCP process aborts with C0000142 (DLL Initialization Failed) error,
- // when "running as user"
- _logger.WriteLine("Granting access to window station");
- try
- {
- IntPtr windowStation = UnsafeNativeMethods.GetProcessWindowStation();
- GrantAccess(windowStation, (int)WindowStationRights.AllAccess);
- }
- catch (Exception e)
- {
- throw _logger.WriteException(new SessionLocalException(_session, "Error granting access to window station", e));
- }
- _logger.WriteLine("Granting access to desktop");
- try
- {
- IntPtr desktop = UnsafeNativeMethods.GetThreadDesktop(UnsafeNativeMethods.GetCurrentThreadId());
- GrantAccess(desktop, (int)DesktopRights.AllAccess);
- }
- catch (Exception e)
- {
- throw _logger.WriteException(new SessionLocalException(_session, "Error granting access to desktop", e));
- }
- }
- #endif
- _logger.WriteLine("Starting \"{0}\" {1}", _process.StartInfo.FileName, _process.StartInfo.Arguments);
- _process.Start();
- _logger.WriteLine("Started process {0}", _process.Id);
- _thread = new Thread(ProcessEvents)
- {
- IsBackground = true
- };
- _thread.Start();
- }
- }
- // Handles returned by GetProcessWindowStation and GetThreadDesktop should not be closed
- internal class NoopSafeHandle : SafeHandle
- {
- public NoopSafeHandle(IntPtr handle) :
- base(handle, false)
- {
- }
- public override bool IsInvalid
- {
- get { return false; }
- }
- protected override bool ReleaseHandle()
- {
- return true;
- }
- }
- #if !NETSTANDARD
- private void GrantAccess(IntPtr handle, int accessMask)
- {
- using (SafeHandle safeHandle = new NoopSafeHandle(handle))
- {
- GenericSecurity security =
- new GenericSecurity(false, ResourceType.WindowObject, safeHandle, AccessControlSections.Access);
- security.AddAccessRule(
- new GenericAccessRule(new NTAccount(_session.ExecutableProcessUserName), accessMask, AccessControlType.Allow));
- security.Persist(safeHandle, AccessControlSections.Access);
- }
- }
- #endif
- private void ProcessExited(object sender, EventArgs e)
- {
- _logger.WriteLine("Process {0} exited with exit code {1}", _process.Id, _process.ExitCode);
- }
- private bool AbortedOrExited()
- {
- if (_abort)
- {
- _logger.WriteLine("Aborted");
- return true;
- }
- else if (_process.HasExited)
- {
- _logger.WriteLine("Exited");
- return true;
- }
- else
- {
- return false;
- }
- }
- private void ProcessEvents()
- {
- using (_logger.CreateCallstack())
- {
- try
- {
- while (!AbortedOrExited())
- {
- _logger.WriteLineLevel(1, "Waiting for request event");
- // Keep in sync with a delay in SessionLogReader.DoRead
- if (_requestEvent.WaitOne(100, false))
- {
- _logger.WriteLineLevel(1, "Got request event");
- ProcessEvent();
- }
- if (_logger.LogLevel >= 1)
- {
- _logger.WriteLine(string.Format(CultureInfo.InvariantCulture, "2nd generation collection count: {0}", GC.CollectionCount(2)));
- _logger.WriteLine(string.Format(CultureInfo.InvariantCulture, "Total memory allocated: {0}", GC.GetTotalMemory(false)));
- }
- }
- }
- catch (Exception e)
- {
- _logger.WriteLine("Error while processing events");
- _logger.WriteException(e);
- throw;
- }
- }
- }
- private void ProcessEvent()
- {
- using (_logger.CreateCallstack())
- {
- using (ConsoleCommStruct commStruct = AcquireCommStruct())
- {
- switch (commStruct.Event)
- {
- case ConsoleEvent.Print:
- ProcessPrintEvent(commStruct.PrintEvent);
- break;
- case ConsoleEvent.Input:
- ProcessInputEvent(commStruct.InputEvent);
- break;
- case ConsoleEvent.Choice:
- ProcessChoiceEvent(commStruct.ChoiceEvent);
- break;
- case ConsoleEvent.Title:
- ProcessTitleEvent(commStruct.TitleEvent);
- break;
- case ConsoleEvent.Init:
- ProcessInitEvent(commStruct.InitEvent);
- break;
- case ConsoleEvent.Progress:
- ProcessProgressEvent(commStruct.ProgressEvent);
- break;
- case ConsoleEvent.TransferOut:
- ProcessTransferOutEvent(commStruct.TransferOutEvent);
- break;
- case ConsoleEvent.TransferIn:
- ProcessTransferInEvent(commStruct.TransferInEvent);
- break;
- default:
- throw _logger.WriteException(new NotImplementedException());
- }
- }
- _responseEvent.Set();
- _logger.WriteLineLevel(1, "Response event set");
- }
- }
- private void ProcessChoiceEvent(ConsoleChoiceEventStruct e)
- {
- using (_logger.CreateCallstack())
- {
- _logger.WriteLine(
- "Options: [{0}], Timer: [{1}], Timeouting: [{2}], Timeouted: [{3}], Break: [{4}]",
- e.Options, e.Timer, e.Timeouting, e.Timeouted, e.Break);
- QueryReceivedEventArgs args = new QueryReceivedEventArgs
- {
- Message = e.Message
- };
- _session.ProcessChoice(args);
- if (args.SelectedAction == QueryReceivedEventArgs.Action.None)
- {
- if (e.Timeouting)
- {
- Thread.Sleep((int)e.Timer);
- e.Result = e.Timeouted;
- }
- else
- {
- e.Result = e.Break;
- }
- }
- else if (args.SelectedAction == QueryReceivedEventArgs.Action.Continue)
- {
- if (e.Timeouting)
- {
- Thread.Sleep((int)e.Timer);
- e.Result = e.Timeouted;
- }
- else
- {
- e.Result = e.Continue;
- }
- }
- else if (args.SelectedAction == QueryReceivedEventArgs.Action.Abort)
- {
- e.Result = e.Break;
- }
- _logger.WriteLine("Options Result: [{0}]", e.Result);
- }
- }
- private void ProcessTitleEvent(ConsoleTitleEventStruct e)
- {
- using (_logger.CreateCallstack())
- {
- _logger.WriteLine("Not-supported title event [{0}]", e.Title);
- }
- }
- private void ProcessInputEvent(ConsoleInputEventStruct e)
- {
- using (_logger.CreateCallstack())
- {
- while (!AbortedOrExited())
- {
- lock (_input)
- {
- if (_input.Count > 0)
- {
- e.Str = _input[0];
- e.Result = true;
- _input.RemoveAt(0);
- Print(false, false, _log[0] + "\n");
- _log.RemoveAt(0);
- return;
- }
- }
- _inputEvent.WaitOne(100, false);
- }
- }
- }
- private void Print(bool fromBeginning, bool error, string message)
- {
- if (fromBeginning && ((message.Length == 0) || (message[0] != '\n')))
- {
- _lastFromBeginning = message;
- _logger.WriteLine("Buffered from-beginning message [{0}]", _lastFromBeginning);
- OutputDataReceived?.Invoke(this, null);
- }
- else
- {
- if (!string.IsNullOrEmpty(_lastFromBeginning))
- {
- AddToOutput(_lastFromBeginning, false);
- _lastFromBeginning = null;
- }
- if (fromBeginning && (message.Length > 0) && (message[0] == '\n'))
- {
- AddToOutput("\n", false);
- _lastFromBeginning = message.Substring(1);
- _logger.WriteLine("Buffered from-beginning message [{0}]", _lastFromBeginning);
- }
- else
- {
- AddToOutput(message, error);
- }
- }
- }
- private void AddToOutput(string message, bool error)
- {
- string[] lines = (_incompleteLine + message).Split(new[] { '\n' });
- _incompleteLine = lines[lines.Length - 1];
- for (int i = 0; i < lines.Length - 1; ++i)
- {
- OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs(lines[i], error));
- }
- }
- private void ProcessPrintEvent(ConsolePrintEventStruct e)
- {
- _logger.WriteLineLevel(1, string.Format(CultureInfo.CurrentCulture, "Print: {0}", e.Message));
- Print(e.FromBeginning, e.Error, e.Message);
- }
- private void ProcessInitEvent(ConsoleInitEventStruct e)
- {
- using (_logger.CreateCallstack())
- {
- if (!e.UseStdErr ||
- (e.BinaryOutput != ConsoleInitEventStruct.StdInOut.Binary) ||
- (e.BinaryInput != ConsoleInitEventStruct.StdInOut.Binary))
- {
- throw _logger.WriteException(new InvalidOperationException("Unexpected console interface options"));
- }
- e.InputType = 3; // pipe
- e.OutputType = 3; // pipe
- e.WantsProgress = _session.WantsProgress;
- }
- }
- private void ProcessProgressEvent(ConsoleProgressEventStruct e)
- {
- using (_logger.CreateCallstack())
- {
- _logger.WriteLine(
- "File Name [{0}] - Directory [{1}] - Overall Progress [{2}] - File Progress [{3}] - CPS [{4}]",
- e.FileName, e.Directory, e.OverallProgress, e.FileProgress, e.CPS);
- if (!_cancel)
- {
- FileTransferProgressEventArgs args = new FileTransferProgressEventArgs();
- switch (e.Operation)
- {
- case ConsoleProgressEventStruct.ProgressOperation.Copy:
- args.Operation = ProgressOperation.Transfer;
- break;
- default:
- throw _logger.WriteException(new ArgumentOutOfRangeException("Unknown progress operation", (Exception)null));
- }
- switch (e.Side)
- {
- case ConsoleProgressEventStruct.ProgressSide.Local:
- args.Side = ProgressSide.Local;
- break;
- case ConsoleProgressEventStruct.ProgressSide.Remote:
- args.Side = ProgressSide.Remote;
- break;
- default:
- throw _logger.WriteException(new ArgumentOutOfRangeException("Unknown progress side", (Exception)null));
- }
- args.FileName = e.FileName;
- args.Directory = e.Directory;
- args.OverallProgress = ((double)e.OverallProgress) / 100;
- args.FileProgress = ((double)e.FileProgress) / 100;
- args.CPS = (int)e.CPS;
- args.Cancel = false;
- _session.ProcessProgress(args);
- }
- if (_cancel)
- {
- e.Cancel = true;
- }
- }
- }
- private void ProcessTransferOutEvent(ConsoleTransferEventStruct e)
- {
- using (_logger.CreateCallstack())
- {
- _logger.WriteLine("Len [{0}]", e.Len);
- if (StdOut == null)
- {
- throw _logger.WriteException(new InvalidOperationException("Unexpected data"));
- }
- int len = (int)e.Len;
- if (len > 0)
- {
- StdOut.WriteInternal(e.Data, 0, len);
- _logger.WriteLine("Data written to the buffer");
- }
- else
- {
- StdOut.CloseWrite();
- _logger.WriteLine("Data buffer closed");
- }
- }
- }
- private void ProcessTransferInEvent(ConsoleTransferEventStruct e)
- {
- using (_logger.CreateCallstack())
- {
- _logger.WriteLine("Len [{0}]", e.Len);
- if (StdIn == null)
- {
- throw _logger.WriteException(new InvalidOperationException("Unexpected data request"));
- }
- try
- {
- int len = (int)e.Len;
- len = StdIn.Read(e.Data, 0, len);
- _logger.WriteLine("{0} bytes read", len);
- e.Len = (uint)len;
- }
- catch (Exception ex)
- {
- _logger.WriteLine("Error reading data stream");
- _logger.WriteException(ex);
- e.Error = true;
- }
- }
- }
- private void InitializeConsole()
- {
- using (_logger.CreateCallstack())
- {
- int attempts = 0;
- Random random = new Random();
- int process = Process.GetCurrentProcess().Id;
- do
- {
- if (attempts > MaxAttempts)
- {
- throw _logger.WriteException(new SessionLocalException(_session, "Cannot find unique name for event object."));
- }
- int instanceNumber = random.Next(1000);
- _instanceName = string.Format(CultureInfo.InvariantCulture, "_{0}_{1}_{2}", process, GetHashCode(), instanceNumber);
- _logger.WriteLine("Trying event {0}", _instanceName);
- if (!TryCreateEvent(ConsoleEventRequest + _instanceName, out _requestEvent))
- {
- _logger.WriteLine("Event {0} is not unique", _instanceName);
- _requestEvent.Close();
- _requestEvent = null;
- }
- else
- {
- _logger.WriteLine("Event {0} is unique", _instanceName);
- _responseEvent = CreateEvent(ConsoleEventResponse + _instanceName);
- _cancelEvent = CreateEvent(ConsoleEventCancel + _instanceName);
- string fileMappingName = ConsoleMapping + _instanceName;
- _fileMapping = CreateFileMapping(fileMappingName);
- if (Marshal.GetLastWin32Error() == UnsafeNativeMethods.ERROR_ALREADY_EXISTS)
- {
- throw _logger.WriteException(new SessionLocalException(_session, string.Format(CultureInfo.InvariantCulture, "File mapping {0} already exists", fileMappingName)));
- }
- if (_fileMapping.IsInvalid)
- {
- throw _logger.WriteException(new SessionLocalException(_session, string.Format(CultureInfo.InvariantCulture, "Cannot create file mapping {0}", fileMappingName)));
- }
- }
- ++attempts;
- }
- while (_requestEvent == null);
- using (ConsoleCommStruct commStruct = AcquireCommStruct())
- {
- commStruct.InitHeader();
- }
- if (_session.GuardProcessWithJobInternal)
- {
- string jobName = ConsoleJob + _instanceName;
- _job = new Job(_logger, jobName);
- }
- }
- }
- private SafeFileHandle CreateFileMapping(string fileMappingName)
- {
- unsafe
- {
- IntPtr securityAttributesPtr = IntPtr.Zero;
- #if !NETSTANDARD
- // We use the EventWaitHandleSecurity only to generate the descriptor binary form
- // that does not differ for object types, so we abuse the existing "event handle" implementation,
- // not to have to create the file mapping SecurityAttributes via P/Invoke.
- // .NET 4 supports MemoryMappedFile and MemoryMappedFileSecurity natively already
- EventWaitHandleSecurity security = CreateSecurity((EventWaitHandleRights)FileMappingRights.AllAccess);
- if (security != null)
- {
- SecurityAttributes securityAttributes = new SecurityAttributes();
- securityAttributes.nLength = (uint)Marshal.SizeOf(securityAttributes);
- byte[] descriptorBinaryForm = security.GetSecurityDescriptorBinaryForm();
- byte * buffer = stackalloc byte[descriptorBinaryForm.Length];
- for (int i = 0; i < descriptorBinaryForm.Length; i++)
- {
- buffer[i] = descriptorBinaryForm[i];
- }
- securityAttributes.lpSecurityDescriptor = (IntPtr)buffer;
- int length = Marshal.SizeOf(typeof(SecurityAttributes));
- securityAttributesPtr = Marshal.AllocHGlobal(length);
- Marshal.StructureToPtr(securityAttributes, securityAttributesPtr, false);
- }
- #endif
- return
- UnsafeNativeMethods.CreateFileMapping(
- new SafeFileHandle(new IntPtr(-1), true), securityAttributesPtr, FileMapProtection.PageReadWrite, 0,
- ConsoleCommStruct.Size, fileMappingName);
- }
- }
- private ConsoleCommStruct AcquireCommStruct()
- {
- return new ConsoleCommStruct(_session, _fileMapping);
- }
- private bool TryCreateEvent(string name, out EventWaitHandle ev)
- {
- _logger.WriteLine("Creating event {0}", name);
- string securityDesc;
- #if !NETSTANDARD
- EventWaitHandleSecurity security = CreateSecurity(EventWaitHandleRights.FullControl);
- ev = new EventWaitHandle(false, EventResetMode.AutoReset, name, out bool createdNew, security);
- securityDesc = (security != null ? security.GetSecurityDescriptorSddlForm(AccessControlSections.All) : "none");
- #else
- ev = new EventWaitHandle(false, EventResetMode.AutoReset, name, out bool createdNew);
- securityDesc = "not impl";
- #endif
- _logger.WriteLine(
- "Created event {0} with handle {1} with security {2}, new {3}",
- name, ev.SafeWaitHandle.DangerousGetHandle(), securityDesc, createdNew);
- return createdNew;
- }
- #if !NETSTANDARD
- private EventWaitHandleSecurity CreateSecurity(EventWaitHandleRights eventRights)
- {
- EventWaitHandleSecurity security = null;
- // When "running as user", we have to grant the target user permissions to the objects (events and file mapping) explicitly
- if (!string.IsNullOrEmpty(_session.ExecutableProcessUserName))
- {
- security = new EventWaitHandleSecurity();
- IdentityReference si;
- try
- {
- si = new NTAccount(_session.ExecutableProcessUserName);
- }
- catch (Exception e)
- {
- throw _logger.WriteException(new SessionLocalException(_session, string.Format(CultureInfo.CurrentCulture, "Error resolving account {0}", _session.ExecutableProcessUserName), e));
- }
- EventWaitHandleAccessRule rule =
- new EventWaitHandleAccessRule(
- si, eventRights, AccessControlType.Allow);
- security.AddAccessRule(rule);
- }
- return security;
- }
- #endif
- private EventWaitHandle CreateEvent(string name)
- {
- if (!TryCreateEvent(name, out EventWaitHandle ev))
- {
- throw _logger.WriteException(new SessionLocalException(_session, string.Format(CultureInfo.InvariantCulture, "Event {0} already exists", name)));
- }
- return ev;
- }
- private void TestEventClosed(string name)
- {
- if (_session.TestHandlesClosedInternal)
- {
- _logger.WriteLine("Testing that event {0} is closed", name);
- if (TryCreateEvent(name, out EventWaitHandle ev))
- {
- ev.Close();
- }
- else
- {
- _logger.WriteLine("Exception: Event {0} was not closed yet", name);
- }
- }
- }
- private void AddInput(string str, string log)
- {
- Type structType = typeof(ConsoleInputEventStruct);
- FieldInfo strField = structType.GetField("Str");
- object[] attributes = strField.GetCustomAttributes(typeof(MarshalAsAttribute), false);
- if (attributes.Length != 1)
- {
- throw _logger.WriteException(new InvalidOperationException("MarshalAs attribute not found for ConsoleInputEventStruct.Str"));
- }
- MarshalAsAttribute marshalAsAttribute = (MarshalAsAttribute)attributes[0];
- if (marshalAsAttribute.SizeConst <= str.Length)
- {
- throw _logger.WriteException(
- new SessionLocalException(
- _session,
- string.Format(CultureInfo.CurrentCulture, "Input [{0}] is too long ({1} limit)", str, marshalAsAttribute.SizeConst)));
- }
- lock (_input)
- {
- _input.Add(str);
- _log.Add(log);
- _inputEvent.Set();
- }
- }
- public void ExecuteCommand(string command, string log)
- {
- using (_logger.CreateCallstack())
- {
- _cancel = false;
- AddInput(command, log);
- }
- }
- public void Close()
- {
- using (_logger.CreateCallstack())
- {
- int timeout;
- #if DEBUG
- // in debug build, we expect the winscp.exe to run in tracing mode, being very slow
- timeout = 10000;
- #else
- timeout = 2000;
- #endif
- _logger.WriteLine("Waiting for process to exit ({0} ms)", timeout);
- if (!_process.WaitForExit(timeout))
- {
- _logger.WriteLine("Killing process");
- _process.Kill();
- }
- }
- }
- public void Dispose()
- {
- using (_logger.CreateCallstack())
- {
- lock (_lock)
- {
- if (_session.TestHandlesClosedInternal)
- {
- _logger.WriteLine("Will test that handles are closed");
- }
- _abort = true;
- if (_thread != null)
- {
- _thread.Join();
- _thread = null;
- }
- if (_process != null)
- {
- _process.Dispose();
- _process = null;
- }
- if (_requestEvent != null)
- {
- _requestEvent.Close();
- TestEventClosed(ConsoleEventRequest + _instanceName);
- }
- if (_responseEvent != null)
- {
- _responseEvent.Close();
- TestEventClosed(ConsoleEventResponse + _instanceName);
- }
- if (_cancelEvent != null)
- {
- _cancelEvent.Close();
- TestEventClosed(ConsoleEventCancel + _instanceName);
- }
- if (_fileMapping != null)
- {
- _fileMapping.Dispose();
- _fileMapping = null;
- if (_session.TestHandlesClosedInternal)
- {
- _logger.WriteLine("Testing that file mapping is closed");
- string fileMappingName = ConsoleMapping + _instanceName;
- SafeFileHandle fileMapping = CreateFileMapping(fileMappingName);
- if (Marshal.GetLastWin32Error() == UnsafeNativeMethods.ERROR_ALREADY_EXISTS)
- {
- _logger.WriteLine("Exception: File mapping {0} was not closed yet", fileMappingName);
- }
- if (!fileMapping.IsInvalid)
- {
- fileMapping.Dispose();
- }
- }
- }
- if (_inputEvent != null)
- {
- _inputEvent.Close();
- _inputEvent = null;
- }
- if (_job != null)
- {
- _job.Dispose();
- _job = null;
- }
- }
- }
- }
- private string GetExecutablePath()
- {
- using (_logger.CreateCallstack())
- {
- string executablePath = _session.ExecutablePath;
- string result;
- if (!string.IsNullOrEmpty(executablePath))
- {
- if (!File.Exists(executablePath))
- {
- throw _logger.WriteException(new SessionLocalException(_session, $"{executablePath} does not exists."));
- }
- result = executablePath;
- }
- else
- {
- result = FindExecutable(_session);
- }
- return result;
- }
- }
- // Should be moved to Session class
- internal static string FindExecutable(Session session)
- {
- Logger logger = session.Logger;
- string executablePath;
- List<string> paths = [];
- string assemblyPath = GetAssemblyPath(logger);
- // If the assembly is not loaded from a file, look to the path of the process execuable
- // (particularly useful for single-file bundles)
- // (also limited this way not to for example look into powershell.exe folder)
- string processPath = !string.IsNullOrEmpty(assemblyPath) ? null : Path.GetDirectoryName(Logger.GetProcessPath());
- if (!TryFindExecutableInPath(logger, paths, assemblyPath, out executablePath) &&
- !TryFindExecutableInPath(logger, paths, GetEntryAssemblyPath(logger), out executablePath) &&
- !TryFindExecutableInPath(logger, paths, processPath, out executablePath) &&
- #if !NETSTANDARD
- !TryFindExecutableInPath(logger, paths, GetInstallationPath(RegistryHive.CurrentUser), out executablePath) &&
- !TryFindExecutableInPath(logger, paths, GetInstallationPath(RegistryHive.LocalMachine), out executablePath) &&
- #endif
- !TryFindExecutableInPath(logger, paths, GetDefaultInstallationPath(), out executablePath))
- {
- string pathsStr = string.Join(", ", paths);
- string filename = ExeExecutableFileName;
- string message =
- $"The {filename} executable was not found at any of the inspected locations ({pathsStr}). " +
- $"You may use Session.ExecutablePath property to explicitly set path to {filename}.";
- throw logger.WriteException(new SessionLocalException(session, message));
- }
- return executablePath;
- }
- private static string GetDefaultInstallationPath()
- {
- string programFiles;
- if (IntPtr.Size == 8)
- {
- programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
- }
- else
- {
- programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
- }
- return Path.Combine(programFiles, "WinSCP");
- }
- #if !NETSTANDARD
- private static string GetInstallationPath(RegistryHive hive)
- {
- RegistryKey baseKey = RegistryKey.OpenBaseKey(hive, RegistryView.Registry32);
- RegistryKey key = baseKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\winscp3_is1");
- string result = (key != null) ? (string)key.GetValue("Inno Setup: App Path") : null;
- return result;
- }
- #endif
- private static bool TryFindExecutableInPath(Logger logger, List<string> paths, string path, out string result)
- {
- if (string.IsNullOrEmpty(path))
- {
- result = null;
- }
- else if (paths.Contains(path, StringComparer.CurrentCultureIgnoreCase))
- {
- logger.WriteLine($"Already searched {path}");
- result = null;
- }
- else
- {
- paths.Add(path);
- string executablePath = Path.Combine(path, ExeExecutableFileName);
- if (File.Exists(executablePath))
- {
- result = executablePath;
- logger.WriteLine("Executable found in {0}", executablePath);
- }
- else
- {
- result = null;
- logger.WriteLine("Executable not found in {0}", executablePath);
- }
- }
- return (result != null);
- }
- private static string GetAssemblyPath(Logger logger)
- {
- return DoGetAssemblyPath(logger.GetAssemblyFilePath());
- }
- private static string GetEntryAssemblyPath(Logger logger)
- {
- return DoGetAssemblyPath(logger.GetEntryAssemblyFilePath());
- }
- private static string DoGetAssemblyPath(string codeBasePath)
- {
- string path = null;
- if (!string.IsNullOrEmpty(codeBasePath))
- {
- path = Path.GetDirectoryName(codeBasePath);
- Debug.Assert(path != null);
- }
- return path;
- }
- [DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
- public static extern int GetFileVersionInfoSize(string lptstrFilename, out int handle);
- [DllImport("kernel32.dll", SetLastError = true)]
- static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, uint dwFlags);
- [DllImport("kernel32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- static extern bool FreeLibrary(IntPtr hModule);
- [DllImport("kernel32.dll", SetLastError = true)]
- static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
- [DllImport("kernel32.dll", SetLastError = true)]
- static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);
- private string GetVersionStr(FileVersionInfo version)
- {
- return $"{version.FileVersion}, product {version.ProductName} version is {version.ProductVersion}";
- }
- private void CheckVersion(string exePath, FileVersionInfo assemblyVersion)
- {
- using (_logger.CreateCallstack())
- {
- if (assemblyVersion == null)
- {
- _logger.WriteLine("Assembly version not known, cannot check version");
- }
- else if (assemblyVersion.ProductVersion == AssemblyConstants.UndefinedProductVersion)
- {
- _logger.WriteLine("Undefined assembly version, cannot check version");
- }
- else
- {
- DateTime dateTime = File.GetLastWriteTimeUtc(exePath);
- _logger.WriteLine($"Timestamp of {exePath} is {dateTime}");
- bool known;
- var cacheKey = new Tuple<string, DateTime>(exePath, dateTime);
- lock (_versionInfoCache)
- {
- known = _versionInfoCache.TryGetValue(cacheKey, out FileVersionInfo version);
- if (known)
- {
- _logger.WriteLine(
- $"Cached version of {exePath} is {GetVersionStr(version)}, and it was already deemed compatible");
- }
- else
- {
- _logger.WriteLine($"Executable version is not cached yet, cache size is {_versionInfoCache.Count}");
- }
- }
- if (!known)
- {
- FileVersionInfo version = FileVersionInfo.GetVersionInfo(exePath);
- _logger.WriteLine($"Version of {exePath} is {GetVersionStr(version)}");
- bool incompatible = assemblyVersion.ProductVersion != version.ProductVersion;
- Exception accessException = null;
- if (incompatible || _logger.Logging)
- {
- try
- {
- using (File.OpenRead(exePath))
- {
- }
- long size = new FileInfo(exePath).Length;
- _logger.WriteLine($"Size of the executable file is {size}");
- int verInfoSize = GetFileVersionInfoSize(exePath, out int handle);
- if (verInfoSize == 0)
- {
- throw new Exception($"Cannot retrieve {exePath} version info", new Win32Exception());
- }
- else
- {
- _logger.WriteLine($"Size of the executable file version info is {verInfoSize}");
- }
- }
- catch (Exception e)
- {
- _logger.WriteLine("Accessing executable file failed");
- _logger.WriteException(e);
- accessException = e;
- }
- }
- if (_session.DisableVersionCheck)
- {
- _logger.WriteLine("Version check disabled (not recommended)");
- }
- else if (incompatible)
- {
- if (_logger.Logging)
- {
- try
- {
- using (SHA256 SHA256 = SHA256.Create())
- using (FileStream stream = File.OpenRead(exePath))
- {
- string sha256 = string.Concat(Array.ConvertAll(SHA256.ComputeHash(stream), b => b.ToString("x2")));
- _logger.WriteLine($"SHA-256 of the executable file is {sha256}");
- }
- }
- catch (Exception e)
- {
- _logger.WriteLine("Calculating SHA-256 of the executable file failed");
- _logger.WriteException(e);
- }
- try
- {
- IntPtr library = LoadLibraryEx(exePath, IntPtr.Zero, 0x00000002); // LOAD_LIBRARY_AS_DATAFILE
- if (library == IntPtr.Zero)
- {
- _logger.WriteLine("Cannot load");
- _logger.WriteException(new Win32Exception());
- }
- else
- {
- IntPtr resource = FindResource(library, "#1", "#16");
- if (resource == IntPtr.Zero)
- {
- _logger.WriteLine("Cannot find version resource");
- _logger.WriteException(new Win32Exception());
- }
- else
- {
- uint resourceSize = SizeofResource(library, resource);
- if (resourceSize == 0)
- {
- _logger.WriteLine("Cannot find size of version resource");
- _logger.WriteException(new Win32Exception());
- }
- else
- {
- _logger.WriteLine($"Version resource size is {resourceSize}");
- }
- }
- FreeLibrary(library);
- }
- }
- catch (Exception e)
- {
- _logger.WriteLine("Querying version resource failed");
- _logger.WriteException(e);
- }
- }
- string message;
- if (string.IsNullOrEmpty(version.ProductVersion) && (accessException != null))
- {
- message = $"Cannot use {exePath}";
- }
- else
- {
- message =
- $"The version of {exePath} ({version.ProductVersion}) does not match " +
- $"version of this assembly {_logger.GetAssemblyFilePath()} ({assemblyVersion.ProductVersion}).";
- }
- throw _logger.WriteException(new SessionLocalException(_session, message, accessException));
- }
- else
- {
- lock (_versionInfoCache)
- {
- _logger.WriteLine("Caching executable version");
- _versionInfoCache[cacheKey] = version;
- }
- }
- }
- }
- }
- }
- public void WriteStatus()
- {
- _logger.WriteLine("{0} - exists [{1}]", ExecutablePath, File.Exists(ExecutablePath));
- }
- public void RequestCallstack()
- {
- using (_logger.CreateCallstack())
- {
- lock (_lock)
- {
- if (_process == null)
- {
- _logger.WriteLine("Process is closed already");
- }
- else
- {
- try
- {
- string eventName = string.Format(CultureInfo.InvariantCulture, "WinSCPCallstack{0}", _process.Id);
- using (EventWaitHandle ev = EventWaitHandle.OpenExisting(eventName))
- {
- _logger.WriteLine("Setting event {0}", eventName);
- ev.Set();
- string callstackFileName = string.Format(CultureInfo.InvariantCulture, "{0}.txt", eventName);
- string callstackPath = Path.Combine(Path.GetTempPath(), callstackFileName);
- int timeout = 2000;
- while (!File.Exists(callstackPath))
- {
- if (timeout < 0)
- {
- string message = string.Format(CultureInfo.CurrentCulture, "Timeout waiting for callstack file {0} to be created ", callstackPath);
- throw new TimeoutException(message);
- }
- int step = 50;
- timeout -= 50;
- Thread.Sleep(step);
- }
- _logger.WriteLine("Callstack file {0} has been created", callstackPath);
- // allow writting to be finished
- Thread.Sleep(100);
- _logger.WriteLine(File.ReadAllText(callstackPath));
- File.Delete(callstackPath);
- }
- }
- catch (Exception e)
- {
- _logger.WriteException(e);
- }
- }
- }
- }
- }
- public void Cancel()
- {
- _cancel = true;
- }
- private const int MaxAttempts = 10;
- private const string ConsoleMapping = "WinSCPConsoleMapping";
- private const string ConsoleEventRequest = "WinSCPConsoleEventRequest";
- private const string ConsoleEventResponse = "WinSCPConsoleEventResponse";
- private const string ConsoleEventCancel = "WinSCPConsoleEventCancel";
- private const string ConsoleJob = "WinSCPConsoleJob";
- private const string ExeExecutableFileName = "winscp.exe";
- private Process _process;
- private readonly object _lock = new object();
- private readonly Logger _logger;
- private readonly Session _session;
- private EventWaitHandle _requestEvent;
- private EventWaitHandle _responseEvent;
- private EventWaitHandle _cancelEvent;
- private SafeFileHandle _fileMapping;
- private string _instanceName;
- private Thread _thread;
- private bool _abort;
- private string _lastFromBeginning;
- private string _incompleteLine;
- private readonly List<string> _input = new List<string>();
- private readonly List<string> _log = new List<string>();
- private AutoResetEvent _inputEvent = new AutoResetEvent(false);
- private Job _job;
- private bool _cancel;
- private static readonly Dictionary<Tuple<string, DateTime>, FileVersionInfo> _versionInfoCache = new Dictionary<Tuple<string, DateTime>, FileVersionInfo>();
- }
- }
|