/* Copyright (c) Citrix Systems Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Threading; namespace XenAdmin.Core { public class NamedPipes { [Flags] public enum FileAccess : uint { GenericRead = 0x80000000, GenericWrite = 0x40000000, GenericExecute = 0x20000000, GenericAll = 0x10000000 } [Flags] public enum FileShare : uint { None = 0x00000000, /// /// Enables subsequent open operations on an object to request read access. /// Otherwise, other processes cannot open the object if they request read access. /// If this flag is not specified, but the object has been opened for read access, the function fails. /// Read = 0x00000001, /// /// Enables subsequent open operations on an object to request write access. /// Otherwise, other processes cannot open the object if they request write access. /// If this flag is not specified, but the object has been opened for write access, the function fails. /// Write = 0x00000002, /// /// Enables subsequent open operations on an object to request delete access. /// Otherwise, other processes cannot open the object if they request delete access. /// If this flag is not specified, but the object has been opened for delete access, the function fails. /// Delete = 0x00000004 } public enum FileMode : uint { /// /// Creates a new file. The function fails if a specified file exists. /// CreateNew = 1, /// /// Creates a new file, always. /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies. /// CreateAlways = 2, /// /// Opens a file. The function fails if the file does not exist. /// OpenExisting = 3, /// /// Opens a file, always. /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW. /// OpenAlways = 4, /// /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist. /// The calling process must open the file with the GENERIC_WRITE access right. /// TruncateExisting = 5 } [Flags] public enum FileAttributes : uint { Readonly = 0x00000001, Hidden = 0x00000002, System = 0x00000004, Directory = 0x00000010, Archive = 0x00000020, Device = 0x00000040, Normal = 0x00000080, Temporary = 0x00000100, SparseFile = 0x00000200, ReparsePoint = 0x00000400, Compressed = 0x00000800, Offline = 0x00001000, NotContentIndexed = 0x00002000, Encrypted = 0x00004000, Write_Through = 0x80000000, Overlapped = 0x40000000, NoBuffering = 0x20000000, RandomAccess = 0x10000000, SequentialScan = 0x08000000, DeleteOnClose = 0x04000000, BackupSemantics = 0x02000000, PosixSemantics = 0x01000000, OpenReparsePoint = 0x00200000, OpenNoRecall = 0x00100000, FirstPipeInstance = 0x00080000 } /// /// Duplex pipe access. /// private const uint PIPE_ACCESS_DUPLEX = 0x00000003; /// /// Pipe blocking mode. /// private const uint PIPE_WAIT = 0x00000000; /// /// Pipe read mode of type Message. /// private const uint PIPE_READMODE_MESSAGE = 0x00000002; /// /// Message pipe type. /// private const uint PIPE_TYPE_MESSAGE = 0x00000004; /// /// Unlimited server pipe instances. /// private const uint PIPE_UNLIMITED_INSTANCES = 255; /// /// Waits indefinitely when connecting to a pipe. /// private const uint NMPWAIT_WAIT_FOREVER = 0xffffffff; /// /// Invalid operating system handle. /// private const int INVALID_HANDLE_VALUE = -1; /// /// More data is available. /// private const int ERROR_MORE_DATA = 234; /// /// There is a process on other end of the pipe. /// private const int ERROR_PIPE_CONNECTED = 535; [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CreateNamedPipe(String pipeName, uint openMode, uint pipeMode, uint maxInstances, uint outputBufferSize, uint inputBufferSize, uint timeoutInterval, IntPtr pipeSecurityDescriptor); /// /// Enables a named pipe server process to wait for a client /// process to connect to an instance of a named pipe. /// /// Handle to the server end of a named pipe instance. /// Pointer to an /// Overlapped object. /// If the function succeeds, the return value is nonzero. [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool ConnectNamedPipe(IntPtr namedPipeHandle, ref NativeOverlapped overlapped); /// /// Disconnects the server end of a named pipe instance from a client process. /// /// Handle to an instance of a named pipe. /// If the function succeeds, the return value is nonzero. [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool DisconnectNamedPipe(IntPtr hHandle); /// /// Reads data from a file, starting at the position indicated by the file pointer. /// /// Handle to the file to be read. /// Pointer to the buffer that receives the data read from the file. /// Number of bytes to be read from the file. /// Pointer to the variable that receives the number of bytes read. /// Pointer to an /// Overlapped object. /// The ReadFile function returns when one of the following /// conditions is met: a write operation completes on the write end of /// the pipe, the number of bytes requested has been read, or an error occurs. [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool ReadFile(IntPtr fileHandle, [Out] byte[] dataBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [In] ref NativeOverlapped overlapped); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool WriteFile(IntPtr fileHandle, [Out] byte[] dataBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, [In] ref NativeOverlapped overlapped); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool PeekNamedPipe(IntPtr handle, [Out] byte[] buffer, uint nBufferSize, out uint bytesRead, out uint bytesAvail, out uint BytesLeftThisMessage); [DllImport("kernel32.dll")] private static extern bool CallNamedPipe(string lpNamedPipeName, byte[] lpInBuffer, uint nInBufferSize, [Out] byte[] lpOutBuffer, uint nOutBufferSize, out uint lpBytesRead, uint nTimeOut); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr hObject); private const string STOP_LISTENING_MSG = "stop-listening-on-pipe"; public class Pipe { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public readonly IntPtr Handle; public EventHandler Read; private Thread pipeThread; private readonly string pipePath; private volatile bool run; /// If creating the pipe failed for any reason. public Pipe(string path) { this.pipePath = path; Handle = CreateNamedPipe(pipePath, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 512, 512, NMPWAIT_WAIT_FOREVER, IntPtr.Zero); if (Handle.ToInt32() == INVALID_HANDLE_VALUE) { // Throw last win32 exception throw new Win32Exception(); } } /// /// Starts reading from this pipe on a new background thread. This call returns immediately. /// Whenever data is read from the pipe, the Read event is fired. /// public void BeginRead() { if (pipeThread == null) { run = true; pipeThread = new Thread((ThreadStart)BackgroundPipeThread); pipeThread.Name = "Named pipe thread"; pipeThread.IsBackground = true; pipeThread.Start(); } } /// /// May block for up to 30 seconds. /// public void Disconnect() { // Now connect to our own NamedPipe and sent a disconnect message if (pipeThread != null) { byte[] msg = Encoding.UTF8.GetBytes(STOP_LISTENING_MSG); byte[] rcv = new byte[0]; UInt32 bytesRead; UInt32 timeout = 30 * 1000; CallNamedPipe(pipePath, msg, (UInt32)msg.Length, rcv, (UInt32)rcv.Length, out bytesRead, timeout); } } private void BackgroundPipeThread() { while (run) { try { NativeOverlapped overlapped = new NativeOverlapped(); if (!ConnectNamedPipe(Handle, ref overlapped)) { Win32Exception exn = new Win32Exception(); if (exn.NativeErrorCode == ERROR_PIPE_CONNECTED) { // This is OK. It simply means a remote process has connected to the // named pipe since we did the CreateNamedPipe. } else { throw new Win32Exception("ConnectNamedPipe failed", exn); } } ProcessPipeMessage(); if (!DisconnectNamedPipe(Handle)) { throw new Win32Exception("DisconnectNamedPipe failed", new Win32Exception()); } } catch (Exception exn) { log.Error("Error in named pipe thread.", exn); // Sanity: pause after errors to prevent massive log spamming/CPU drain // in the event of an infinite error loop Thread.Sleep(1000); } } // Close pipe handle to destroy it if (CloseHandle(Handle)) { log.Debug("Closed NamedPipe handle"); } else { log.Warn(new Win32Exception("CloseHandle() in NamedPipes failed", new Win32Exception())); } log.Debug("NamedPipe thread exited"); } private void ProcessPipeMessage() { NativeOverlapped overlapped = new NativeOverlapped(); byte[] readMessage; using (MemoryStream ms = new MemoryStream()) { while (true) { // Loop as follows: // Peek at the pipe to see how much data is available // Read the data and append it to the buffer // If we get ERROR_MORE_DATA, repeat UInt32 bytesRead, bytesAvailable, bytesLeft; // First peek into the pipe to see how much data is waiting. byte[] peekBuf = new byte[0]; if (!PeekNamedPipe(Handle, peekBuf, (UInt32)peekBuf.Length, out bytesRead, out bytesAvailable, out bytesLeft)) { throw new Win32Exception( string.Format("PeekNamedPipe failed. bytesRead={0} bytesAvailable={1} bytesLeft={2}", bytesRead, bytesAvailable, bytesLeft), new Win32Exception()); } // Sanity check: throw away message if it is > 1 MB if (ms.Length + bytesLeft > 1024 * 1024) { throw new Exception("Indecently large message sent into named pipe: rejecting."); } // Now allocate a buffer of the correct size and read in the message. byte[] readBuf = new byte[bytesAvailable]; if (!ReadFile(Handle, readBuf, (UInt32)readBuf.Length, out bytesRead, ref overlapped)) { Win32Exception exn = new Win32Exception(); if (exn.NativeErrorCode == ERROR_MORE_DATA) { // The peek may have looked into the pipe before the write operation // had completed, and so reported a message size before the whole // message was sent. In this case we need to go round the loop again // with a larger buffer. ms.Write(readBuf, 0, (int)bytesRead); continue; } else { throw new Win32Exception( string.Format("ReadFile failed. readBuf.Length={0} bytesRead={1}", readBuf.Length, bytesRead), exn); } } else { ms.Write(readBuf, 0, (int)bytesRead); break; } } readMessage = ms.ToArray(); } // Now perform a zero-byte write. This causes the CallNamedPipe call to // return successfully in the splash screen. UInt32 bytesWritten; byte[] toWrite = new byte[0]; if (!WriteFile(Handle, toWrite, (UInt32)toWrite.Length, out bytesWritten, ref overlapped)) { throw new Win32Exception( string.Format("WriteFile failed. toWrite.Length={0} bytesWritten={1}", toWrite.Length, bytesWritten), new Win32Exception()); } PipeReadEventArgs e = new PipeReadEventArgs(readMessage); if (e.Message == STOP_LISTENING_MSG) { log.Debug("NamedPipe thread was told to stop listening"); run = false; return; } // Now inform any listeners of the received data. if (Read != null) { Read(null, e); } } } public class PipeReadEventArgs : EventArgs { public readonly string Message; public PipeReadEventArgs(byte[] bytes) { Message = Encoding.Unicode.GetString(bytes); } } } }