////////////////////////////////////////////////// // Created by Ivan Latunov - IvanWeb.com // //----------------------------------------------// // This program is free software. You can // // redistribute it and/or modify it as you wish // ////////////////////////////////////////////////// using System; using sscs.communication.win.InterProcessComm; using System.Runtime.InteropServices; using System.Text; using HANDLE = System.IntPtr; namespace sscs.communication.win.NamedPipes { #region Comments /// <summary> /// A utility class that exposes named pipes operations. /// </summary> /// <remarks> /// This class uses the exposed exposed kernel32.dll methods by the /// <see cref="AppModule.NamedPipes.NamedPipeNative">NamedPipeNative</see> class /// to provided controlled named pipe functionality. /// </remarks> #endregion public sealed class NamedPipeWrapper { public const int TOKEN_QUERY = 0X00000008; const int ERROR_NO_MORE_ITEMS = 259; #region Comments /// <summary> /// The number of retries when creating a pipe or connecting to a pipe. /// </summary> #endregion private const int ATTEMPTS = 2; #region Comments /// <summary> /// Wait time for the /// <see cref="AppModule.NamedPipes.NamedPipeNative.WaitNamedPipe">NamedPipeNative.WaitNamedPipe</see> /// operation. /// </summary> #endregion private const int WAIT_TIME = 5000; #region Comments /// <summary> /// Reads a string from a named pipe using the UTF8 encoding. /// </summary> /// <param name="handle">The pipe handle.</param> /// <param name="maxBytes">The maximum bytes to read.</param> /// <returns>A UTF8 string.</returns> /// <remarks>This function uses /// <see cref="AppModule.NamedPipes.NamedPipeWrapper.ReadBytes">AppModule.NamedPipes.ReadBytes</see> /// to read the bytes from the pipe and then converts them to string.<br/><br/> /// The first four bytes of the pipe data are expected to contain /// the data length of the message. This method first reads those four /// bytes and converts them to integer. It then continues to read from the pipe using /// the extracted data length. /// </remarks> #endregion public static string Read(PipeHandle handle, int maxBytes) { string returnVal = ""; byte[] bytes = ReadBytes(handle, maxBytes); if (bytes != null) { returnVal = System.Text.Encoding.UTF8.GetString(bytes); } return returnVal; } #region Comments /// <summary> /// Reads the bytes from a named pipe. /// </summary> /// <param name="handle">The pipe handle.</param> /// <param name="maxBytes">The maximum bytes to read.</param> /// <returns>An array of bytes.</returns> /// <remarks>This method expects that the first four bytes in the pipe define /// the length of the data to read. If the data length is greater than /// <b>maxBytes</b> the method returns null.<br/><br/> /// The first four bytes of the pipe data are expected to contain /// the data length of the message. This method first reads those four /// bytes and converts them to integer. It then continues to read from the pipe using /// the extracted data length. /// </remarks> #endregion public static byte[] ReadBytes(PipeHandle handle, int maxBytes) { byte[] numReadWritten = new byte[4]; byte[] intBytes = new byte[4]; byte[] msgBytes = null; int len; // Set the Handle state to Reading handle.State = InterProcessConnectionState.Reading; // Read the first four bytes and convert them to integer bool bReadSuccessful = true; try { bReadSuccessful = NamedPipeNative.ReadFile(handle.Handle, intBytes, 4, numReadWritten, 0); } catch (Exception) { // Console.WriteLine("Error on ReadFile "+e.ToString()); } if (bReadSuccessful) { len = BitConverter.ToInt32(intBytes, 0); msgBytes = new byte[len]; // Read the rest of the data if (!NamedPipeNative.ReadFile(handle.Handle, msgBytes, (uint)len, numReadWritten, 0)) { handle.State = InterProcessConnectionState.Error; throw new NamedPipeIOException("Error reading from pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } } else { handle.State = InterProcessConnectionState.Error; throw new NamedPipeIOException("Error reading from pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } handle.State = InterProcessConnectionState.ReadData; if (len > maxBytes) { return null; } return msgBytes; } #region Comments /// <summary> /// Writes a string to a named pipe. /// </summary> /// <param name="handle">The pipe handle.</param> /// <param name="text">The text to write to the pipe.</param> /// <remarks>This method converts the text into an array of bytes, using the /// UTF8 encoding and the uses /// <see cref="AppModule.NamedPipes.NamedPipeWrapper.WriteBytes">AppModule.NamedPipes.WriteBytes</see> /// to write to the pipe.<br/><br/> /// When writing to a pipe the method first writes four bytes that define the data length. /// It then writes the whole message.</remarks> #endregion public static void Write(PipeHandle handle, string text) { WriteBytes(handle, System.Text.Encoding.UTF8.GetBytes(text)); } #region Comments /// <summary> /// Writes an array of bytes to a named pipe. /// </summary> /// <param name="handle">The pipe handle.</param> /// <param name="bytes">The bytes to write.</param> /// <remarks>If we try bytes array we attempt to write is empty then this method write a space character to the pipe. This is necessary because the other end of the pipe uses a blocking Read operation so we must write someting.<br/><br/> /// The bytes length is restricted by the <b>maxBytes</b> parameter, which is done primarily for security reasons.<br/><br/> /// When writing to a pipe the method first writes four bytes that define the data length. /// It then writes the whole message.</remarks> #endregion public static void WriteBytes(PipeHandle handle, byte[] bytes) { byte[] numReadWritten = new byte[4]; uint len; if (bytes == null) { bytes = new byte[0]; } if (bytes.Length == 0) { bytes = new byte[1]; bytes = System.Text.Encoding.UTF8.GetBytes(" "); } // Get the message length len = (uint)bytes.Length; handle.State = InterProcessConnectionState.Writing; // Write four bytes that define the message length if (NamedPipeNative.WriteFile(handle.Handle, BitConverter.GetBytes(len), 4, numReadWritten, 0)) { // Write the whole message if (!NamedPipeNative.WriteFile(handle.Handle, bytes, len, numReadWritten, 0)) { handle.State = InterProcessConnectionState.Error; throw new NamedPipeIOException("Error writing to pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } } else { handle.State = InterProcessConnectionState.Error; throw new NamedPipeIOException("Error writing to pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } handle.State = InterProcessConnectionState.Flushing; Flush(handle); handle.State = InterProcessConnectionState.FlushedData; } #region Comments /// <summary> /// Tries to connect to a named pipe on the same machine. /// </summary> /// <param name="pipeName">The name of the pipe.</param> /// <param name="handle">The resulting pipe handle.</param> /// <returns>Return true if the attempt succeeds.</returns> /// <remarks>This method is used mainly when stopping the pipe server. It unblocks the existing pipes, which wait for client connection.</remarks> #endregion public static bool TryConnectToPipe(string pipeName, out PipeHandle handle) { return TryConnectToPipe(pipeName, ".", out handle); } #region Comments /// <summary> /// Tries to connect to a named pipe. /// </summary> /// <param name="pipeName">The name of the pipe.</param> /// <param name="serverName">The name of the server.</param> /// <param name="handle">The resulting pipe handle.</param> /// <returns>Return true if the attempt succeeds.</returns> /// <remarks>This method is used mainly when stopping the pipe server. It unblocks the existing pipes, which wait for client connection.</remarks> #endregion public static bool TryConnectToPipe(string pipeName, string serverName, out PipeHandle handle) { handle = new PipeHandle(); // Build the pipe name string string name = @"\\" + serverName + @"\pipe\" + pipeName; handle.State = InterProcessConnectionState.ConnectingToServer; // Try to connect to a server pipe handle.Handle = NamedPipeNative.CreateFile(name, NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, 0, null, NamedPipeNative.OPEN_EXISTING, 0, 0); if (handle.Handle.ToInt32() != NamedPipeNative.INVALID_HANDLE_VALUE) { handle.State = InterProcessConnectionState.ConnectedToServer; return true; } else { handle.State = InterProcessConnectionState.Error; return false; } } #region Comments /// <summary> /// Connects to a server named pipe on the same machine. /// </summary> /// <param name="pipeName">The pipe name.</param> /// <returns>The pipe handle, which also contains the pipe state.</returns> /// <remarks>This method is used by clients to establish a pipe connection with a server pipe.</remarks> #endregion public static PipeHandle ConnectToPipe(string pipeName) { return ConnectToPipe(pipeName, "."); } #region Comments /// <summary> /// Connects to a server named pipe. /// </summary> /// <param name="pipeName">The pipe name.</param> /// <param name="serverName">The server name.</param> /// <returns>The pipe handle, which also contains the pipe state.</returns> /// <remarks>This method is used by clients to establish a pipe connection with a server pipe.</remarks> #endregion public static PipeHandle ConnectToPipe(string pipeName, string serverName) { PipeHandle handle = new PipeHandle(); // Build the name of the pipe. string name = @"\\" + serverName + @"\PIPE\" + pipeName; for (int i = 1; i<=ATTEMPTS; i++) { handle.State = InterProcessConnectionState.ConnectingToServer; // Try to connect to the server handle.Handle = NamedPipeNative.CreateFile(name, NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, 0, null, NamedPipeNative.OPEN_EXISTING, 0, 0); if (handle.Handle.ToInt32() != NamedPipeNative.INVALID_HANDLE_VALUE) { // The client managed to connect to the server pipe handle.State = InterProcessConnectionState.ConnectedToServer; // Set the read mode of the pipe channel uint mode = NamedPipeNative.PIPE_READMODE_MESSAGE; if (NamedPipeNative.SetNamedPipeHandleState(handle.Handle, ref mode, IntPtr.Zero, IntPtr.Zero)) { break; } if (i >= ATTEMPTS) { handle.State = InterProcessConnectionState.Error; throw new NamedPipeIOException("Error setting read mode on pipe " + name + " . Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } } if (i >= ATTEMPTS) { if (NamedPipeNative.GetLastError() != NamedPipeNative.ERROR_PIPE_BUSY) { handle.State = InterProcessConnectionState.Error; // After a certain number of unsuccessful attempt raise an exception throw new NamedPipeIOException("Error connecting to pipe " + name + " . Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } else { handle.State = InterProcessConnectionState.Error; throw new NamedPipeIOException("Pipe " + name + " is too busy. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } } else { // The pipe is busy so lets wait for some time and try again if (NamedPipeNative.GetLastError() == NamedPipeNative.ERROR_PIPE_BUSY) NamedPipeNative.WaitNamedPipe(name, WAIT_TIME); } } return handle; } #region Comments /// <summary> /// Creates a server named pipe. /// </summary> /// <param name="name">The name of the pipe.</param> /// <param name="outBuffer">The size of the outbound buffer.</param> /// <param name="inBuffer">The size of the inbound buffer.</param> /// <returns>The pipe handle.</returns> #endregion public static PipeHandle Create(string name, uint outBuffer, uint inBuffer) { if ((name.IndexOf("pipe") < 0) && (name.IndexOf("PIPE") < 0)) name = @"\\.\pipe\" + name; PipeHandle handle = new PipeHandle(); for (int i = 1; i<=ATTEMPTS; i++) { handle.State = InterProcessConnectionState.Creating; handle.Handle = NamedPipeNative.CreateNamedPipe( name, NamedPipeNative.PIPE_ACCESS_DUPLEX, NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE | NamedPipeNative.PIPE_WAIT, NamedPipeNative.PIPE_UNLIMITED_INSTANCES, outBuffer, inBuffer, NamedPipeNative.NMPWAIT_WAIT_FOREVER, IntPtr.Zero); if (handle.Handle.ToInt32() != NamedPipeNative.INVALID_HANDLE_VALUE) { handle.State = InterProcessConnectionState.Created; break; } if (i >= ATTEMPTS) { handle.State = InterProcessConnectionState.Error; throw new NamedPipeIOException("Error creating named pipe " + name + " . Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } } return handle; } #region Comments /// <summary> /// Starts waiting for client connections. /// </summary> /// <remarks> /// Blocks the current execution until a client pipe attempts to establish a connection. /// </remarks> /// <param name="handle">The pipe handle.</param> #endregion public static void Connect(PipeHandle handle) { handle.State = InterProcessConnectionState.WaitingForClient; bool connected = NamedPipeNative.ConnectNamedPipe(handle.Handle, null); handle.State = InterProcessConnectionState.ConnectedToClient; if (!connected && NamedPipeNative.GetLastError() != NamedPipeNative.ERROR_PIPE_CONNECTED) { handle.State = InterProcessConnectionState.Error; throw new NamedPipeIOException("Error connecting pipe. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } } #region Comments /// <summary> /// Returns the number of instances of a named pipe. /// </summary> /// <param name="handle">The pipe handle.</param> /// <returns>The number of instances.</returns> #endregion public static uint NumberPipeInstances(PipeHandle handle) { uint curInstances = 0; if (NamedPipeNative.GetNamedPipeHandleState(handle.Handle, IntPtr.Zero, ref curInstances, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)) { return curInstances; } else { throw new NamedPipeIOException("Error getting the pipe state. Internal error: " + NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError()); } } #region Comments /// <summary> /// Closes a named pipe and releases the native handle. /// </summary> /// <param name="handle">The pipe handle.</param> #endregion public static void Close(PipeHandle handle) { handle.State = InterProcessConnectionState.Closing; NamedPipeNative.CloseHandle(handle.Handle); handle.Handle = IntPtr.Zero; handle.State = InterProcessConnectionState.Closed; } #region Comments /// <summary> /// Flushes all the data in a named pipe. /// </summary> /// <param name="handle">The pipe handle.</param> #endregion public static void Flush(PipeHandle handle) { handle.State = InterProcessConnectionState.Flushing; NamedPipeNative.FlushFileBuffers(handle.Handle); handle.State = InterProcessConnectionState.FlushedData; } #region Comments /// <summary> /// Disconnects a server named pipe from the client. /// </summary> /// <remarks> /// Server pipes can be reused by first disconnecting them from the client and then /// calling the <see cref="AppModule.NamedPipes.NamedPipeWrapper.Connect">Connect</see> /// method to start listening. This improves the performance as it is not necessary /// to create new pipe handles. /// </remarks> /// <param name="handle">The pipe handle.</param> #endregion public static void Disconnect(PipeHandle handle) { handle.State = InterProcessConnectionState.Disconnecting; NamedPipeNative.DisconnectNamedPipe(handle.Handle); handle.State = InterProcessConnectionState.Disconnected; } #region Comments /// <summary> /// Private constructor. /// </summary> #endregion private NamedPipeWrapper() {} // Client USERID stuff // 1. call ImpersonateNamedPipeClient(hPipe) // 2. call OpenThreadToken(GetCurrentThread(), // TOKEN_QUERY | TOKEN_QUERY_SOURCE, // FALSE, // phUserToken); public static int ImpersonateNamePipeClient(IntPtr hPipeHandle) { int rcode = NamedPipeNative.ImpersonateNamedPipeClient(hPipeHandle); return rcode; } static int PerformDump(HANDLE token) { StringBuilder sb = new StringBuilder(); NamedPipeNative.TOKEN_USER tokUser; const int bufLength = 256; IntPtr tu = Marshal.AllocHGlobal( bufLength ); int cb = bufLength; if (NamedPipeNative.GetTokenInformation( token, NamedPipeNative.TOKEN_INFORMATION_CLASS.TokenUser, tu, cb, ref cb )) Console.WriteLine("GetTokenInformation successful"); else { Console.WriteLine("GetTokenInformation NOT successful"); uint error = NamedPipeNative.GetLastError(); Console.WriteLine("error" + error.ToString()); } tokUser = (NamedPipeNative.TOKEN_USER) Marshal.PtrToStructure(tu, typeof(NamedPipeNative.TOKEN_USER) ); //sb.Append(DumpAccountSid(tokUser.User.Sid)); IntPtr pUserID = tokUser.User.Sid; //Console.WriteLine("UserID: " + pUserID); DumpAccountSid(pUserID); Marshal.FreeHGlobal( tu ); tu = Marshal.AllocHGlobal(bufLength); cb = bufLength; // get token states NamedPipeNative.TOKEN_STATISTICS stats; if (NamedPipeNative.GetTokenInformation(token, NamedPipeNative.TOKEN_INFORMATION_CLASS.TokenStatistics, tu, cb, ref cb)) { stats = (NamedPipeNative.TOKEN_STATISTICS) Marshal.PtrToStructure(tu, typeof(NamedPipeNative.TOKEN_STATISTICS)); Console.WriteLine("UserLow: "+stats.AuthenticationId.LowPart.ToString()); Console.WriteLine("UserHigh: "+stats.AuthenticationId.HighPart.ToString()); } else { Console.WriteLine("failed"); } return (int)pUserID; } static string DumpAccountSid(IntPtr SID) { int cchAccount = 0; int cchDomain = 0; int snu = 0 ; StringBuilder sb = new StringBuilder(); // Caller allocated buffer StringBuilder Account= null; StringBuilder Domain = null; bool ret = NamedPipeNative.LookupAccountSid(null, SID, Account, ref cchAccount, Domain, ref cchDomain, ref snu); if ( ret == true ) if ( Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS ) return "Error"; try { Account = new StringBuilder( cchAccount ); Domain = new StringBuilder( cchDomain ); ret = NamedPipeNative.LookupAccountSid(null, SID, Account, ref cchAccount, Domain, ref cchDomain, ref snu); if (ret) { sb.Append(Domain); sb.Append(@"\\"); sb.Append(Account); } else Console.WriteLine("logon account (no name) "); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { } string SidString = null; NamedPipeNative.ConvertSidToStringSid(SID, ref SidString); sb.Append("\nSID: "); sb.Append(SidString); Console.WriteLine("Acct info: "+ sb.ToString()); return sb.ToString(); } public static int GetLocalUserID(PipeHandle handle, ref int lowPart, ref int highPart, ref string SidString) { int rcode = -1; // get client userID int code = NamedPipeNative.ImpersonateNamedPipeClient(handle.Handle); if (code == 0) { uint lastError = NamedPipeNative.GetLastError(); Console.WriteLine("ImpersonateNamedPipeClient Error: "+rcode.ToString()); return -1; } try { IntPtr hThread = NamedPipeNative.GetCurrentThread(); uint iDesiredInfo = 24; //TOKEN_QUERY | TOKEN_QUERY_SOURCE; IntPtr userToken = Marshal.AllocHGlobal(4); if (NamedPipeNative.OpenThreadToken(hThread, iDesiredInfo, true, out userToken)) { StringBuilder sb = new StringBuilder(); NamedPipeNative.TOKEN_USER tokUser; const int bufLength = 256; IntPtr tu = Marshal.AllocHGlobal( bufLength ); int cb = bufLength; if (NamedPipeNative.GetTokenInformation( userToken, NamedPipeNative.TOKEN_INFORMATION_CLASS.TokenUser, tu, cb, ref cb )) { tokUser = (NamedPipeNative.TOKEN_USER) Marshal.PtrToStructure(tu, typeof(NamedPipeNative.TOKEN_USER) ); IntPtr pUserID = tokUser.User.Sid; Marshal.FreeHGlobal( tu ); // get SID //string SidString = null; NamedPipeNative.ConvertSidToStringSid(pUserID, ref SidString); // get token states tu = Marshal.AllocHGlobal(bufLength); cb = bufLength; NamedPipeNative.TOKEN_STATISTICS stats; if (NamedPipeNative.GetTokenInformation(userToken, NamedPipeNative.TOKEN_INFORMATION_CLASS.TokenStatistics, tu, cb, ref cb)) { stats = (NamedPipeNative.TOKEN_STATISTICS) Marshal.PtrToStructure(tu, typeof(NamedPipeNative.TOKEN_STATISTICS)); // copy low and high part lowPart = stats.AuthenticationId.LowPart; highPart = stats.AuthenticationId.HighPart; rcode = -1; } } else { Console.WriteLine("GetTokenInformation NOT successful"); uint error = NamedPipeNative.GetLastError(); Console.WriteLine("error" + error.ToString()); } // close handle NamedPipeNative.CloseHandle(hThread); NamedPipeNative.RevertToSelf(); } else { int lastError = Marshal.GetLastWin32Error(); uint errorcode = NamedPipeNative.GetLastError(); Console.WriteLine("OpenThreadToken Error: "+ errorcode.ToString() + " code2: "+rcode.ToString()); } } catch (Exception ex) { int error = Marshal.GetLastWin32Error(); Console.WriteLine(ex.ToString()); return rcode; } // end return rcode; } } }