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.*/
}