using System; using System.Diagnostics; using System.Text; using System.IO; using System.Net.Sockets; using System.Net; using System.Security.Cryptography; using System.Security.Permissions; using System.Security.Principal; using System.Threading; using System.Timers; using System.Linq; namespace IndianHealthService.BMXNet.Net { /// /// BMXNetBroker implements low-level socket connectivity to RPMS databases. /// The VA RPC Broker must be running on the RPMS server in order for /// BMXNetBroker to connect. /// [DnsPermission(SecurityAction.Assert, Unrestricted = true)] internal class BMXNetSessionSocketConnection : BMXNetSessionConnection { public static int DefaultSendTimeout = 20000; public static int DefaultReceiveTimeout = 40000; private int _sendTimeout = 0; public override int SendTimeout { get { if (_sendTimeout != 0) { return _sendTimeout; } return this.ConnectionSpec.SendTimeout == 0 ? DefaultSendTimeout : this.ConnectionSpec.SendTimeout; } set { _sendTimeout = value; } } private int _receiveTimeout = 0; /// /// Set and retrieve the timeout, in milliseconds, to receive a response from the RPMS server. /// If the retrieve time exceeds the timeout, an exception will be thrown and the connection will be closed. /// The default is 30 seconds. /// public override int ReceiveTimeout { get { if (_receiveTimeout != 0) { return _receiveTimeout; } return this.ConnectionSpec.ReceiveTimeout == 0 ? DefaultReceiveTimeout : this.ConnectionSpec.ReceiveTimeout; } set { _receiveTimeout = value; } } public override Encoding ConnectionEncoding // Default Encoding determined by Windows; typically a Windows Code Page { get; set; } public BMXNetSessionSocketConnection(BMXNetBroker aBroker):base(aBroker) { m_sWKID = "BMX"; m_sWINH = ""; m_sPRCH = ""; m_sWISH = ""; m_cHDR = ADEBHDR(m_sWKID,m_sWINH,m_sPRCH,m_sWISH); ConnectionEncoding = Encoding.Default; } #region RPX Fields private string m_sWKID; private string m_sWISH; private string m_sPRCH; private string m_sWINH; private string m_cHDR; private bool m_bConnected; private TcpClient m_pCommSocket; private string m_sNameSpace = ""; #endregion RPX Fields public TcpClient Socket { get { return this.m_pCommSocket; } } /// /// Returns index of first instance of sSubString in sString. /// If sSubString not found, returns -1. /// /// [SocketPermissionAttribute(SecurityAction.Assert, Access = "Connect", Host = "All", Port = "All", Transport = "All")] protected virtual void OpenConnectionCommon() { try { TcpClient connector = null; try { connector = new TcpClient(); connector.SendTimeout = this.SendTimeout; connector.ReceiveTimeout = this.ReceiveTimeout; connector.Connect(this.ServerAddress, this.ServerPort); } catch (SocketException exSocket) { string s = exSocket.Message + exSocket.StackTrace; throw new BMXNetException(s); } //Prepare & send the connect message string cSend = "TCPconnect^" + m_sNameSpace + "^^"; int nLen = cSend.Length; string sLen = nLen.ToString(); sLen = sLen.PadLeft(5, '0'); cSend = "{BMX}" + sLen + cSend; NetworkStream ns = connector.GetStream(); byte[] sendBytes = Encoding.ASCII.GetBytes(cSend); ns.Write(sendBytes,0,sendBytes.Length); m_pCommSocket = connector; return; } catch (BMXNetException bmxEx) { throw bmxEx; } catch (Exception ex) { string s = ex.Message + ex.StackTrace; throw new BMXNetException(s); } }//End OpenConnectionCommon private BMXNetSocketConnectionSpec _connectionSpec = null; internal BMXNetSocketConnectionSpec ConnectionSpec { get { return _connectionSpec; } set { _connectionSpec = value; } } [SocketPermissionAttribute(SecurityAction.Assert, Access = "Connect", Host = "All", Port = "All", Transport = "All")] public virtual bool OpenConnection(BMXNetSocketConnectionSpec aSpec) { this.ConnectionSpec = aSpec; try { m_bConnected = false; this.OpenConnectionCommon(); if (this.CheckConnection()) { bool authenicated = false; if (aSpec.UseWindowsAuthentication) { authenicated = this.SendSecurityRequest(aSpec.WindowsIdentity); } else { authenicated = SendSecurityRequest(aSpec.EncryptedAccessVerifyCode); } this.m_bConnected = authenicated; int integerDuz = 0; if (!int.TryParse(this.DUZ, out integerDuz)) { throw new BMXNetException("Invalid DUZ, Unable to authenticate user."); } this.UserName = this.GetUserName(); } return this.IsConnected; } catch (BMXNetException bmxEx) { throw bmxEx; } catch (Exception ex) { string s = ex.Message + ex.StackTrace; throw new BMXNetException(s); } finally { if (!this.IsConnected) { this.PrimitiveCloseConnection(); } } } bool m_bLogging = false; private static void Log(String logMessage, TextWriter w) { w.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString()); w.WriteLine(" :"); w.WriteLine(" :{0}", logMessage); w.WriteLine("-------------------------------"); // Update the underlying file. w.Flush(); } private bool CheckConnection() { try { String cMSG = ADEBLDMsg(m_cHDR, "BMX CONNECT STATUS", ""); #if DEBUG _watch = new Stopwatch(); _watch.Start(); #endif this.SendString(this.Socket, cMSG); String strReceive = ReceiveString(this.Socket); #if DEBUG _watch.Stop(); Debug.WriteLine("Time: " + _watch.ElapsedMilliseconds + " ms"); Debug.WriteLine("---"); _watch = null; #endif String port = BMXNetBroker.Piece(strReceive, "|", 1); String message = BMXNetBroker.Piece(strReceive, "|", 2); if (!message.Contains("OK")) { throw new BMXNetException(message.Length==0 ? strReceive : message); } this.Job = BMXNetBroker.Piece(strReceive, "|", 3); return true; } catch (Exception potentialProblem) { //If there's an issue, assume old BMX 2.x/3.x so this feature is not supports BMX CONNECT STATUS if (potentialProblem.Message.Contains("BMX CONNECT STATUS")) { return true; } else { throw potentialProblem; } } } public override bool IsConnected { get { return this.m_bConnected; } } Stopwatch _watch = new Stopwatch(); protected override String SendReceiveString(string cSendString, string cMult) { #if DEBUG _watch = new Stopwatch(); _watch.Start(); #endif this.SendString(this.m_pCommSocket, cSendString, cMult); Debug.WriteLine("Time After Send: " + _watch.ElapsedMilliseconds + " ms"); string _received = this.ReceiveString(this.m_pCommSocket); #if DEBUG _watch.Stop(); Debug.WriteLine("Time: " + _watch.ElapsedMilliseconds + " ms"); Debug.WriteLine("---"); _watch = null; #endif return _received; } protected virtual void SendString(TcpClient tcpClient, string cSendString) { string sMult = ""; SendString(tcpClient, cSendString, sMult); } protected virtual void SendString(TcpClient tcpClient, string cSendString, string cMult) { #if DEBUG Debug.WriteLine("Sending (T:" + Thread.CurrentThread.ManagedThreadId + "): " + cSendString); #endif String encodedString = this.EncodeSendString(cSendString, cMult); NetworkStream ns = tcpClient.GetStream(); ns.WriteTimeout = this.SendTimeout; byte[] sendBytes = ConnectionEncoding.GetBytes(encodedString); ns.Write(sendBytes,0,sendBytes.Length); if (this.m_bLogging == true) { Log("Sent: " + cSendString, this.m_LogWriter); } return; } private string ReceiveString(TcpClient tcpClient) { #if DEBUG Debug.WriteLine("Time At Start of Receive: " + _watch.ElapsedMilliseconds + " ms"); #endif /******************INIT*****************/ NetworkStream ns = tcpClient.GetStream(); ns.ReadTimeout = this.ReceiveTimeout; //Timeout; throw exception automatically if we time out. /*************SECURITY S-PACKET READ***********************/ int secPackLen = ns.ReadByte(); //Read S-Pack Length Byte byte[] secPackContents = new byte[secPackLen];; if (secPackLen > 0) { ns.Read(secPackContents, 0, secPackLen); } /****************ERROR S-PACKET READ******************/ int errPackLen = ns.ReadByte(); //Read S-Pack Length Byte byte[] errPackConents = new byte[errPackLen];; if (errPackLen > 0) { ns.Read(errPackConents, 0, errPackLen); } //If either one of these exists, then we have an error on the Mumps Database if (secPackLen > 0 || errPackLen > 0) { //We still have to read the Data to empty the tcp OS buffers even if we have security/errors in the DB while (ns.DataAvailable) ns.ReadByte() ; //while loop to empty the buffer -- this is theoretically slow, but that's not important here //Now decode them. string secString = ConnectionEncoding.GetString(secPackContents); string errString = ConnectionEncoding.GetString(errPackConents); throw new BMXNetException("Mumps Database Security/Error: " + secString + " | " + errString); } /*****************DATA STREAM READ*******************/ //Data stream from VISTA ends when we find the EOT (ASCII 4) character MemoryStream mStream = new MemoryStream(1500); // Auto expanding memory storage for what we receive int numberOfBytesRead = 0; bool bFinished = false; // Have we found the End of Transmission (ASCII 4) yet? // Main Read Loop while (!bFinished) //while we are not done { byte[] bReadBuffer = new byte[1500]; //buffer size = MTU. Message MUST be smaller. numberOfBytesRead = ns.Read(bReadBuffer, 0, bReadBuffer.Length); //read data bFinished = FindChar(bReadBuffer, (char)4) > -1; //look for the EOT (ASCII 4) character if (!bFinished) mStream.Write(bReadBuffer, 0, numberOfBytesRead); //if we are not done, put the whole stream in else mStream.Write(bReadBuffer, 0, numberOfBytesRead - 1); //otherwise, number of bytes minus the $C(4) at the end } //At this point, we are done reading from the Network Stream. Now we have to decode the data. //Decode the entire message. string sReadBuffer = ConnectionEncoding.GetString(mStream.ToArray()); //decode String decodedReceiveString = this.DecodeReceiveString(sReadBuffer); if (this.m_bLogging) { Log("Received: " + decodedReceiveString, this.m_LogWriter); } #if DEBUG Debug.WriteLine("Time At End of Receive: " + _watch.ElapsedMilliseconds + " ms"); Debug.WriteLine("Received(T:" + Thread.CurrentThread.ManagedThreadId + "): " + decodedReceiveString.Replace((char)30, (char)10)); #endif return decodedReceiveString; /* OLD CODE NetworkStream ns = tcpClient.GetStream(); ns.ReadTimeout = this.ReceiveTimeout; //TAE: This following is suspect. NetworkSTream Read and ReadTimeout provide //the same behavior. Look at removing in the futuer. For now, this works. int cyclePause = 25; int cycles = 0; DateTime start = DateTime.Now; while (ns.DataAvailable == false) { if (cycles++ > 999) break; if ((DateTime.Now-start).TotalMilliseconds > this.ReceiveTimeout) break; Thread.Sleep(cyclePause); } Debug.Assert(ns.DataAvailable); if (!ns.DataAvailable) { this.Close(); throw new Exception("BMXNetBroker.ReceiveString timeout. Connection Closed."); } byte[] bReadBuffer = new byte[1024]; string sReadBuffer = ""; StringBuilder sbAll = new StringBuilder("", 1024); int numberOfBytesRead = 0; // Incoming message may be larger than the buffer size. bool bFinished = false; bool bStarted = false; int lpBuf = 0; string sError = ""; string sAppError = ""; do { numberOfBytesRead = ns.Read(bReadBuffer, 0, bReadBuffer.Length); if ((numberOfBytesRead == 1)&&(bStarted == false)) { //TAE: This following is suspect. If Read is blocking then this sleep is extra. //This is rarely called Thread.Sleep(15); numberOfBytesRead += ns.Read(bReadBuffer,1, bReadBuffer.Length-1); } if (bStarted == false) { //Process error info at beginning of returned string int nErrLen = bReadBuffer[0]; int nAppLen = bReadBuffer[bReadBuffer[0]+1]; if ((bReadBuffer[2] == 0)&&(bReadBuffer[3] == 0)) { //special case: M error trap invoked in SND^XWBTCPC lpBuf += 2; } sError = Encoding.ASCII.GetString(bReadBuffer, lpBuf + 1, nErrLen); if (sError != "") { sAppError = Encoding.ASCII.GetString(bReadBuffer, lpBuf + 1 + nErrLen + 1, nAppLen); throw new BMXNetException(sError); } sAppError = Encoding.ASCII.GetString(bReadBuffer, lpBuf+1+nErrLen+1, nAppLen); lpBuf += (nErrLen + nAppLen + 2); numberOfBytesRead -= (nErrLen + nAppLen + 2); bStarted = true; } bFinished = FindChar(bReadBuffer, (char)4) > -1; Debug.Assert(numberOfBytesRead > -1); sReadBuffer = Encoding.ASCII.GetString(bReadBuffer, lpBuf, numberOfBytesRead); lpBuf = 0; if (bFinished) { sbAll.Append(sReadBuffer, 0, numberOfBytesRead -1); } else { sbAll.Append(sReadBuffer); } } while(!bFinished); String decodedReceiveString = this.DecodeReceiveString(sbAll.ToString()); if (this.m_bLogging) { Log("Received: " + decodedReceiveString, this.m_LogWriter); } #if DEBUG Debug.WriteLine("Time At End of Receive: " + _watch.ElapsedMilliseconds + " ms"); #endif return decodedReceiveString; */ } private bool SendSecurityRequest(WindowsIdentity winIdentity) { string strReceive = ""; string cMSG; string sTest; //Build AV Call cMSG = ADEBLDMsg(m_cHDR, "BMX AV CODE", winIdentity.Name); #if DEBUG _watch = new Stopwatch(); _watch.Start(); #endif SendString(this.Socket, cMSG); strReceive = ReceiveString(this.Socket); #if DEBUG _watch.Stop(); Debug.WriteLine("Time: " + _watch.ElapsedMilliseconds + " ms"); Debug.WriteLine("---"); _watch = null; #endif sTest = strReceive.Substring(0,3); char[] cDelim = {(char) 13,(char) 10,(char) 0}; string sDelim = new string(cDelim); int nPiece = 1; this.DUZ = M.Piece(strReceive, sDelim , nPiece); if ((this.DUZ.Length==0 ) || ("0".Equals(this.DUZ))) { nPiece = 7; string sReason = M.Piece(strReceive, sDelim, nPiece); throw new Exception(sReason); } return true; } public String EncryptAccessVerifyCode(string anAccessCode, string aVerifyCode) { string accessVerifyPair = anAccessCode.ToUpper() + ";" + aVerifyCode.ToUpper(); return this.EncryptionProvider.Encrypt(accessVerifyPair); } private bool SendSecurityRequest(string encryptedAccessVerifyCode) { string strReceive = ""; string cMSG; cMSG = ADEBLDMsg(m_cHDR, "XUS AV CODE", encryptedAccessVerifyCode); #if DEBUG _watch = new Stopwatch(); _watch.Start(); #endif SendString(this.Socket, cMSG); strReceive = ReceiveString(this.Socket); #if DEBUG _watch.Stop(); Debug.WriteLine("Time: " + _watch.ElapsedMilliseconds + " ms"); Debug.WriteLine("---"); _watch = null; #endif if (strReceive.StartsWith("M ERROR=")) { this.DUZ = "0"; throw new BMXNetException("Server error has occured: " + strReceive); } char[] cDelim = {(char) 13,(char) 10,(char) 0}; string U = new string(cDelim); this.DUZ = M.Piece(strReceive,U, 1); String resultCode = M.Piece(strReceive, U, 2); String resultMessageIndex = M.Piece(strReceive, U, 3); //R(0)=DUZ if sign-on was OK, zero if not OK. //R(1)=(0=OK, 1,2...=Can't sign-on for some reason). //R(2)=verify needs changing. //R(3)=Message. //R(4)=0 //R(5)=count of the number of lines of text, zero if none. //extracall but same code-path as winidentity login if ((this.DUZ == "0") || (this.DUZ == "")) { string sReason = M.Piece(strReceive, U, 7); throw new BMXNetException(sReason); } return true; } public override void Close() { if (m_bConnected) { this.PrimitiveCloseConnection(); } } protected void PrimitiveCloseConnection() { if (this.Socket != null) { SendString(this.Socket, "#BYE#"); this.Socket.Close(); m_pCommSocket = null; } this.ConnectionSpec = null; this.DUZ = ""; m_bConnected=false; } public override string GetLoginFacility(String aDuz) { try { if (!this.IsConnected) { throw new BMXNetException("BMXNetBroker is not connected to RPMS"); } #if DEBUG _watch = new Stopwatch(); _watch.Start(); #endif this.SendString(this.Socket, ADEBLDMsg(m_cHDR, "BMXGetFac", aDuz)); return this.ReceiveString(this.Socket); } catch (BMXNetException bmxEx) { throw new BMXNetException(bmxEx.Message + " || "+bmxEx.StackTrace); } catch (Exception ex) { throw ex; } } protected String GetUserName() { return this.TransmitRPC("BMX USER", this.DUZ); } /// /// Ping the server, will reset read-timeout /// /// Answer the #milliseconds to run or -1 if theres an issue public double ImHereServer() { try { if (this.IsConnected) { DateTime past = DateTime.Now; this.TransmitRPC("BMX IM HERE",""); return (DateTime.Now - past).TotalMilliseconds; } else { return -1; } } catch { return -1; } } #region RPX Properties public bool Connected { get { return m_bConnected; } } public string ServerAddress { get { return this.ConnectionSpec.Server; } } public string ServerNamespace { get { return this.ConnectionSpec.NameSpace; } } public int ServerPort { get { return this.ConnectionSpec.Port; } } #endregion RPX Properties } /* 1 ;;1;Signons not currently allowed on this processor. 2 ;;1;Maximum number of users already signed on to this processor. 3 ;;1;This device has not been defined to the system -- contact system manager. 4 ;;0;Not a valid Windows Identity map value. 5 ;;0;No Access Allowed for this User. 6 ;;0;Invalid device password. 7 ;;0;Device locked due to too many invalid sign-on attempts. 8 ;;1;This device is out of service. 9 ;;0;*** MULTIPLE SIGN-ONS NOT ALLOWED *** 10 ;;1;You don't have access to this device! 11 ;;0;Your access code has been terminated. Please see your site manager! 12 ;;0;VERIFY CODE MUST be changed before continued use. 13 ;;1;This device may only be used outside of this time frame | 14 ;;0;'|' is not a valid UCI! 15 ;;0;'|' is not a valid program name! 16 ;;0;No PRIMARY MENU assigned to user or User is missing KEY to menu! 17 ;;0;Your access to the system is prohibited from |. 18 ;;0;Windows Integrated Security Not Allowed on this port.*/ }