From 588e118469f32a175dec3699c00ea3eca8fb34e7 Mon Sep 17 00:00:00 2001 From: Bob Carroll Date: Thu, 17 Oct 2013 15:07:31 -0700 Subject: [PATCH] import from subversion --- .gitignore | 8 + AssemblyInfo.cpp | 40 ++ C3270Char.cpp | 193 ++++++++ C3270Char.h | 138 ++++++ CClient3270.cpp | 1004 +++++++++++++++++++++++++++++++++++++++++ CClient3270.h | 194 ++++++++ CEbcdicTranslator.cpp | 86 ++++ CEbcdicTranslator.h | 95 ++++ CTelnetClient.cpp | 806 +++++++++++++++++++++++++++++++++ CTelnetClient.h | 241 ++++++++++ CWndMain.cpp | 119 +++++ CWndMain.h | 152 +++++++ CWndMain.resx | 129 ++++++ IBM3270.h | 120 +++++ TelnetProtocol.h | 61 +++ WinTN3270.cpp | 43 ++ WinTN3270.sln | 20 + WinTN3270.vcproj | 312 +++++++++++++ WinTN3270.vcxproj | 154 +++++++ app.ico | Bin 0 -> 1078 bytes app.rc | 63 +++ resource.h | 3 + stdafx.cpp | 7 + stdafx.h | 6 + 24 files changed, 3994 insertions(+) create mode 100644 .gitignore create mode 100644 AssemblyInfo.cpp create mode 100644 C3270Char.cpp create mode 100644 C3270Char.h create mode 100644 CClient3270.cpp create mode 100644 CClient3270.h create mode 100644 CEbcdicTranslator.cpp create mode 100644 CEbcdicTranslator.h create mode 100644 CTelnetClient.cpp create mode 100644 CTelnetClient.h create mode 100644 CWndMain.cpp create mode 100644 CWndMain.h create mode 100644 CWndMain.resx create mode 100644 IBM3270.h create mode 100644 TelnetProtocol.h create mode 100644 WinTN3270.cpp create mode 100644 WinTN3270.sln create mode 100644 WinTN3270.vcproj create mode 100644 WinTN3270.vcxproj create mode 100644 app.ico create mode 100644 app.rc create mode 100644 resource.h create mode 100644 stdafx.cpp create mode 100644 stdafx.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b46e29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.*.swp +Debug/ +Release/ +*.user +*.suo +*.filters +WinTN3270.sdf diff --git a/AssemblyInfo.cpp b/AssemblyInfo.cpp new file mode 100644 index 0000000..5c7220d --- /dev/null +++ b/AssemblyInfo.cpp @@ -0,0 +1,40 @@ +#include "stdafx.h" + +using namespace System; +using namespace System::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; +using namespace System::Security::Permissions; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly:AssemblyTitleAttribute("WinTN3270")]; +[assembly:AssemblyDescriptionAttribute("")]; +[assembly:AssemblyConfigurationAttribute("")]; +[assembly:AssemblyCompanyAttribute("NTID")]; +[assembly:AssemblyProductAttribute("WinTN3270")]; +[assembly:AssemblyCopyrightAttribute("Copyright (c) 2007 Bob Carroll")]; +[assembly:AssemblyTrademarkAttribute("")]; +[assembly:AssemblyCultureAttribute("")]; + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the value or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly:AssemblyVersionAttribute("1.0.*")]; + +[assembly:ComVisible(false)]; + +[assembly:CLSCompliantAttribute(true)]; + +[assembly:SecurityPermission(SecurityAction::RequestMinimum, UnmanagedCode = true)]; diff --git a/C3270Char.cpp b/C3270Char.cpp new file mode 100644 index 0000000..1731567 --- /dev/null +++ b/C3270Char.cpp @@ -0,0 +1,193 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#include "stdafx.h" +#include "C3270Char.h" +#include "CEbcdicTranslator.h" + +using namespace WinTN3270; +using namespace System; +using namespace System::Drawing; + +/*********************************************************** + Creates a NULL character object. +***********************************************************/ +C3270Char::C3270Char() +{ + this->__Constructor(NULL, false); +} + +/*********************************************************** + Creates a character object from a byte. + + @param chChar the EBCDIC character to use +***********************************************************/ +C3270Char::C3270Char(Byte chChar) +{ + this->__Constructor(chChar, false); +} + +/*********************************************************** + Creates a character object from a byte. This is the + start-of-field byte. + + @param chChar the EBCDIC character to use + @param fStartField flag to indicate start-of-field +***********************************************************/ +C3270Char::C3270Char(Byte chChar, bool fStartField) +{ + this->__Constructor(chChar, true); + this->UnserializeAttributes(); +} + +/*********************************************************** + Creates a character object from a byte. This is the real + constructor. + + @param chChar the EBCDIC character to use + @param fStartField flag to indicate start-of-field +***********************************************************/ +void C3270Char::__Constructor(System::Byte chChar, bool fStartField) +{ + m_chChar = (fStartField ? chChar : CEbcdicTranslator::CharToASCII(chChar)); + m_chInitChar = m_chChar; + m_fAutoSkip = false; + m_fModified = false; + m_fNumeric = false; + m_fProtected = false; + m_fStartField = fStartField; + m_nDisplay = 0; +} + +/*********************************************************** + Gets the appropriate color brush. + + @return the brush to paint with +***********************************************************/ +Brush^ C3270Char::GetPaintBrush() +{ + switch (m_nDisplay) { + + /* Bright */ + case DisplayOptions::Bright: + return Brushes::White; + + /* Dark */ + case DisplayOptions::Dark: + return Brushes::Black; + + /* Normal */ + default: + return Brushes::Green; + } +} + +/*********************************************************** + Gets the appropriate color brush. + + @param fBaseColor flag to enable base colors + + @return the brush to paint with +***********************************************************/ +Brush^ C3270Char::GetPaintBrush(bool fBaseColor) +{ + bool fBright = (m_nDisplay == DisplayOptions::Bright); + + /* Get the base color */ + if (!m_fProtected && !fBright) /* Green */ + return Brushes::Green; + else if (!m_fProtected && fBright) /* Red */ + return Brushes::Red; + else if (m_fProtected && !fBright) /* Blue */ + return Brushes::Blue; + else if (m_fProtected && fBright) /* White */ + return Brushes::White; + + return this->GetPaintBrush(); +} + +/*********************************************************** + Creates a string representation of the character object. + + @return a pretty string +***********************************************************/ +String^ C3270Char::ToString() +{ + return this->ToString(false); +} + +/*********************************************************** + Creates a string representation of the character object. + + @param fMasked flag to mask the character + + @return a pretty string +***********************************************************/ +String^ C3270Char::ToString(bool fMasked) +{ + /* Null character displays nothing */ + if (m_chChar == 0 || m_fStartField) + return " "; + + /* Some unprotected fields are dark so we mask the real character */ + if (fMasked && m_chChar != m_chInitChar) + return "*"; + + /* Otherwise output the character array */ + array^ pchString = { m_chChar }; + return gcnew String(pchString); +} + +/*********************************************************** + Reads the field attribute bit field and sets class vars + accordingly. + + @param chAttr the attribute byte +***********************************************************/ +void C3270Char::UnserializeAttributes() +{ + /* Bit 7: Modified Data Tag */ + m_fModified = (m_chChar & 0x01); + + /* Bit 6: Reserved, always zero */ + + /* Bits 4-5: Field Display */ + if ((m_chChar >> 2 & 0x03) == 0x00) + m_nDisplay = DisplayOptions::NormalNoLPDetect; + else if ((m_chChar >> 2 & 0x03) == 0x01) + m_nDisplay = DisplayOptions::NormalLPDetect; + else if ((m_chChar >> 2 & 0x03) == 0x02) + m_nDisplay = DisplayOptions::Bright; + else if ((m_chChar >> 2 & 0x03) == 0x03) + m_nDisplay = DisplayOptions::Dark; + + /* Bit 3: Numeric Only */ + m_fNumeric = (m_chChar >> 4 & 0x01); + + /* Bit 2: Protected */ + m_fProtected = (m_chChar >> 5 & 0x01); + if (m_fNumeric && m_fProtected) + m_fAutoSkip = true; + + /* Bit 1: Unknown (Ignored) */ + + /* Bit 0: Unknown (Ignored) */ +} diff --git a/C3270Char.h b/C3270Char.h new file mode 100644 index 0000000..3218ecc --- /dev/null +++ b/C3270Char.h @@ -0,0 +1,138 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#pragma once + +namespace WinTN3270 +{ + /*********************************************************** + Character object for 3270 terminals. + ***********************************************************/ + public ref class C3270Char + { + + public: /* Public Enums */ + ref class DisplayOptions + { + public: + static const int NormalNoLPDetect = 0; + static const int NormalLPDetect = 1; + static const int Bright = 2; + static const int Dark = 3; + }; + + private: /* Private Member Attributes */ + wchar_t m_chInitChar; + wchar_t m_chChar; + bool m_fAutoSkip; + bool m_fModified; + bool m_fNumeric; + bool m_fProtected; + bool m_fStartField; + int m_nDisplay; + + public: /* Public Properties */ + property bool AutoSkip + { + bool get() + { + return m_fAutoSkip; + } + } + + property wchar_t Character + { + wchar_t get() + { + return m_chChar; + } + + void set(wchar_t chValue) + { + m_chChar = chValue; + } + } + + property int Display + { + int get() + { + return m_nDisplay; + } + } + + property bool Modified + { + bool get() + { + return m_fModified; + } + + void set(bool fValue) + { + /* This is only for the field-start */ + if (!m_fStartField) + return; + + /* Flip bit 7 */ + m_chChar = (fValue ? m_chChar | 0x01 : m_chChar & ~0x01); + m_fModified = fValue; + } + } + + property int NumericOnly + { + int get() + { + return m_fNumeric; + } + } + + property bool Protected + { + bool get() + { + return m_fProtected; + } + } + + property bool StartField + { + bool get() + { + return m_fStartField; + } + } + + private: /* Private Member Functions */ + void UnserializeAttributes(); + + public: /* Public Member Functions */ + C3270Char(); + C3270Char(System::Byte chChar); + C3270Char(System::Byte chChar, bool fStartField); + void __Constructor(System::Byte chChar, bool fStartField); + System::Drawing::Brush^ GetPaintBrush(); + System::Drawing::Brush^ GetPaintBrush(bool fBaseColor); + virtual System::String^ ToString() override; + System::String^ ToString(bool fMasked); + }; +} diff --git a/CClient3270.cpp b/CClient3270.cpp new file mode 100644 index 0000000..b290f2e --- /dev/null +++ b/CClient3270.cpp @@ -0,0 +1,1004 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#include "stdafx.h" +#include "CClient3270.h" +#include "IBM3270.h" +#include "C3270Char.h" +#include "CEbcdicTranslator.h" +#include "stdio.h" + +using namespace WinTN3270; +using namespace System; +using namespace System::Drawing; +using namespace System::Windows::Forms; + +/*********************************************************** + Creates a new TN3270 client instance. + + @param cchIPAddr the IP address of the remote host + @param nPort the remote TCP port +***********************************************************/ +CClient3270::CClient3270(String^ cchIPAddr, int nPort) +{ + this->__Constructor(cchIPAddr, nPort, 0); +} + +/*********************************************************** + Creates a new TN3270 client instance. + + @param cchIPAddr the IP address of the remote host + @param nPort the remote TCP port + @param nModel the terminal model number +***********************************************************/ +CClient3270::CClient3270(String^ cchIPAddr, int nPort, int nModel) +{ + this->__Constructor(cchIPAddr, nPort, nModel); +} + +/*********************************************************** + Disconnects from the remote host. +***********************************************************/ +CClient3270::~CClient3270() +{ + this->Disconnect(); +} + +/*********************************************************** + Creates a new TN3270 client instance. Unlike the above two + functions, this is the real constructor. + + @param cchIPAddr the IP address of the remote host + @param nPort the remote TCP port + @param nModel the terminal model number +***********************************************************/ +void CClient3270::__Constructor(System::String^ cchIPAddr, int nPort, int nModel) +{ + m_mpClient = gcnew CTelnetClient(cchIPAddr, nPort); + + /* Set the screen dimensions */ + switch (nModel) { + + /* 24x80 */ + case 2: + m_nRows = 24; + m_nCols = 80; + break; + + /* 32x80 */ + case 3: + m_nRows = 32; + m_nCols = 80; + break; + + /* 43x80 */ + case 4: + m_nRows = 43; + m_nCols = 80; + break; + + /* 27x132 */ + case 5: + m_nRows = 27; + m_nCols = 132; + break; + + /* Default to model 2 */ + default: + nModel = 2; + m_nRows = 24; + m_nCols = 80; + } + + /* Set the model and term type */ + char* pchModel = (char*)malloc(sizeof(char) + 1); + sprintf_s(pchModel, sizeof(char) + 1, "%d", nModel); + array^ mpTermType = + { 'I', 'B', 'M', '-', '3', '2', '7', '8', '-', *pchModel, '-', 'E' }; + free(pchModel); + m_nModel = nModel; + + /* Set default 3270 options */ + m_mpClient->TerminalType = mpTermType; + m_mpClient->BinaryTransmission = true; + m_mpClient->EndOfRecord = true; + + /* Register callbacks */ + m_mpClient->ReceiveCallbackBinary = + gcnew CTelnetClient::OnReceiveBinary(this, &CClient3270::ReceiveCallback); + m_mpClient->SendCallback = + gcnew CTelnetClient::OnSend(this, &CClient3270::SendCallback); + + m_mpCharBuffer = + gcnew array(this->Size.Height * this->Size.Width); + m_mpOnPaintCbk = nullptr; + m_fBaseColor = true; + m_fKeybLocked = true; + m_fSoundAlaram = false; + m_fStartPrinter = false; + m_nBufferPos = 0; + m_nCursorPos = 0; + m_nActionId = 0; + m_nLineLength = 0; + m_nLastField = 0; + + this->EraseScreen(); +} + +/*********************************************************** + Opens a new connection to the remote host. +***********************************************************/ +void CClient3270::Connect() +{ + this->Connect(false); +} + +/*********************************************************** + Opens a new connection to the remote host. + + @param fSecure enable SSL +***********************************************************/ +void CClient3270::Connect(bool fSecure) +{ + m_mpClient->Connect(fSecure); +} + +/*********************************************************** + Converts the 6-bit buffer address to an integer. + + @param chFirst the first byte of the address + @param chSecond the second byte of the address + + @return the buffer offset as an integer +***********************************************************/ +int CClient3270::ConvertBufferAddress(Byte chFirst, Byte chSecond) +{ + /* Do some bit-shifting voodoo to get the offset */ + return ((chFirst & 0xC0) == 0x00 ? + ((chFirst & 0x3F) << 8) + chSecond : + ((chFirst & 0x3F) << 6) + (chSecond & 0x3F)); +} + +/*********************************************************** + Creates a key entry action for transmission to the + mainframe. + + @param chActionId the action id byte + + @return an action array +***********************************************************/ +array^ CClient3270::CreateActionArray(Byte chActionId) +{ + array^ mpAction = gcnew array(3); + mpAction[0] = chActionId; + mpAction[1] = m_mpAddrTbl[(m_nCursorPos >> 6) & 0x3F]; + mpAction[2] = m_mpAddrTbl[m_nCursorPos & 0x3F]; + + return mpAction; +} + +/*********************************************************** + Terminates the active stream connection. +***********************************************************/ +void CClient3270::Disconnect() +{ + m_mpClient->Disconnect(); +} + +/*********************************************************** + Performs a terminal input action. + + @param chKeyChar the key character + + @return TRUE for success, FALSE otherwise +***********************************************************/ +bool CClient3270::DoInputAction(wchar_t chKeyChar) +{ + array^ mpResp; + + switch (chKeyChar) { + + /* Enter */ + case ConsoleKey::Enter: + /* Send the action */ + mpResp = this->CreateActionArray(IBM3270DS_AID_ENTER); + mpResp = this->MergeModifiedFields(mpResp); + m_mpClient->Send(mpResp); + return true; + + /* Clear Screen */ + case ConsoleKey::F12: + /* Send the action */ + mpResp = gcnew array(1); + mpResp[0] = IBM3270DS_AID_CLEAR; + m_mpClient->Send(mpResp); + } + + return false; +} + +/*********************************************************** + Resets all unprotected fields and jumps to the first. +***********************************************************/ +void CClient3270::EraseAllUnprotected() +{ + bool fInField = false; + + for (int i = 0; i < m_mpCharBuffer->Length; i++) { + /* Look for the start of field character */ + if (m_mpCharBuffer[i]->StartField) { + /* Reset the MDT of unprotected fields */ + fInField = !m_mpCharBuffer[i]->Protected; + if (fInField) + m_mpCharBuffer[i]->Modified = false; + continue; + } + + if (!fInField) + continue; + + /* Null out the character */ + m_mpCharBuffer[i]->Character = 0; + } +} + +/*********************************************************** + Clears the character buffer. +***********************************************************/ +void CClient3270::EraseScreen() +{ + for (int i = 0; i < m_mpCharBuffer->Length; i++) + m_mpCharBuffer[i] = gcnew C3270Char(); + + m_nBufferPos = 0; + m_nCursorPos = 0; + + this->RepaintScreen(); +} + +/*********************************************************** + Finds the field start byte from a given offset. + + @param nOffset the starting offset position + + @return the start-of-field offset, or -1 if it can't find + the SOF byte +***********************************************************/ +int CClient3270::FindStartOfField(int nOffset) +{ + for (int i = nOffset; i >= 0; i--) { + if (m_mpCharBuffer[i]->StartField) + return i; + } + + return -1; +} + +/*********************************************************** + Reads the order byte and any attribute bytes and then + changes the client state accordingly. + + @param mpData the incoming data buffer + @param nOffset the starting offset position + + @return the new starting offset +***********************************************************/ +int CClient3270::InterpretOrder(array^ mpData, int nOffset) +{ + int nBuffAddr; + + /* Bounds check */ + if (mpData == nullptr || mpData->Length == 0 || nOffset >= mpData->Length) + return nOffset; + + while (true) { + switch (mpData[nOffset]) { + + /* Start Field */ + case IBM3270DS_ORDER_SF: + /* The first byte is a field attribute */ + m_mpCharBuffer[m_nBufferPos] = gcnew C3270Char(mpData[nOffset + 1], true); + m_nLastField = m_nBufferPos; + m_nBufferPos++; + + /* Skip to the data */ + nOffset += 2; + break; + + /* Start Field (Extended) */ + case IBM3270DS_ORDER_SFE: + /* Not implemented */ + nOffset += 3; + break; + + /* Set Buffer Address */ + case IBM3270DS_ORDER_SBA: + m_nBufferPos = this->ConvertBufferAddress( + mpData[nOffset + 1], + mpData[nOffset + 2] + ); + nOffset += 3; + + break; + + /* Insert Cursor */ + case IBM3270DS_ORDER_IC: + /* Set the visible cursor position */ + m_nCursorPos = m_nBufferPos; + m_nBufferPos++; + nOffset++; + break; + + /* Erase Unprotected Fields */ + case IBM3270DS_ORDER_EUA: + this->EraseAllUnprotected(); + + /* Jump to the first unprotected field */ + m_nCursorPos = 0; + this->JumpToNextUnprotectedField(); + nOffset += 3; + break; + + /* Modify Field */ + case IBM3270DS_ORDER_MF: + /* Not implemented */ + nOffset += 6; + break; + + /* Repeat to Address */ + case IBM3270DS_ORDER_RA: + nBuffAddr = this->ConvertBufferAddress( + mpData[nOffset + 1], + mpData[nOffset + 2] + ); + this->RepeatToAddress(mpData[nOffset + 3], nBuffAddr); + nOffset += 4; + break; + + /* Set Attribute */ + case IBM3270DS_ORDER_SA: + /* Not implemented */ + nOffset += 3; + break; + + /* Program Tab */ + case IBM3270DS_ORDER_PT: + this->JumpToNextUnprotectedField(); + nOffset++; + break; + + /* Graphic Escape */ + case IBM3270DS_ORDER_GE: + /* Not implemented */ + nOffset += 2; + break; + + /* Not an order byte */ + default: + return nOffset; + } + + if (nOffset >= mpData->Length) + return nOffset; + } +} + +/*********************************************************** + Determines if the given field is protected. + + @param nOffset the offset position in the buffer + + @return TRUE if protected, FALSE otherwise +***********************************************************/ +bool CClient3270::IsProtectedField(int nOffset) +{ + /* If we're at the start of the field... */ + if (m_mpCharBuffer[nOffset]->StartField) + return m_mpCharBuffer[nOffset]->Protected; + + /* ... we're not, so find the SOF first */ + int nFieldStart = this->FindStartOfField(nOffset); + if (nFieldStart == -1) + return false; + + return m_mpCharBuffer[nFieldStart]->Protected; +} + +/*********************************************************** + Moves the cursor to the next unprotected field. +***********************************************************/ +void CClient3270::JumpToNextUnprotectedField() +{ + bool fWrap = false; + + for (int i = m_nCursorPos + 1; i <= m_mpCharBuffer->Length; i++) { + /* Wrap to the front if we're past the end */ + if (i == m_mpCharBuffer->Length) { + fWrap = true; + i = 0; + } + + /* We're looking for start of fields */ + if (fWrap && i >= m_nCursorPos) + break; + if (!m_mpCharBuffer[i]->StartField) + continue; + + /* Skip protected fields */ + if (m_mpCharBuffer[i]->Protected || m_mpCharBuffer[i]->AutoSkip) + continue; + + this->MoveCursor(i - m_nCursorPos + 1); + break; + } +} + +/*********************************************************** + Moves the cursor to the previous unprotected field. +***********************************************************/ +void CClient3270::JumpToPrevUnprotectedField() +{ + bool fWrap = false; + + for (int i = m_nCursorPos - 2; i >= -1; i--) { + /* Wrap to the end if we're past the front */ + if (i == -1) { + fWrap = true; + i = m_mpCharBuffer->Length -1; + } + + /* We're looking for start of fields */ + if (fWrap && i <= m_nCursorPos) + break; + if (!m_mpCharBuffer[i]->StartField) + continue; + + /* Skip protected fields */ + if (m_mpCharBuffer[i]->Protected || m_mpCharBuffer[i]->AutoSkip) + continue; + + this->MoveCursor(-1 * (m_nCursorPos - i) + 1); + break; + } +} + +/*********************************************************** + Simulates a key press on the terminal. + + @param mpArgs the keyboard event arguments +***********************************************************/ +void CClient3270::KeyPress(KeyPressEventArgs^ mpArgs) +{ + this->KeyPress(mpArgs, Keys::None); +} + +/*********************************************************** + Simulates a key press on the terminal. + + @param mpArgs the keyboard event arguments + @param nModifiers bitwise combination of modifier keys +***********************************************************/ +void CClient3270::KeyPress(KeyPressEventArgs^ mpArgs, Keys nModifiers) +{ + C3270Char^ mpChar = m_mpCharBuffer[m_nCursorPos]; + int nFieldStart; + + /* Get the previous cursor position */ + int nCursPrev = (m_nCursorPos == 0 ? m_mpCharBuffer->Length - 1 : m_nCursorPos - 1); + + /* Handle input actions */ + if (DoInputAction(mpArgs->KeyChar)) + return; + + /* Handle Tab */ + if (mpArgs->KeyChar == (wchar_t)ConsoleKey::Tab) { + if (nModifiers == Keys::Shift) + return this->JumpToPrevUnprotectedField(); + else + return this->JumpToNextUnprotectedField(); + } + + /* Handle backspace */ + if (mpArgs->KeyChar == (wchar_t)ConsoleKey::Backspace + && !this->IsProtectedField(nCursPrev) + && !(nCursPrev < m_nCursorPos && m_mpCharBuffer[nCursPrev]->StartField)) + { + /* Erase the last character */ + m_mpCharBuffer[nCursPrev]->Character = 0; + nFieldStart = this->FindStartOfField(nCursPrev); + if (nFieldStart >= 0) + m_mpCharBuffer[nFieldStart]->Modified = true; + return this->MoveCursor(-1); + } + + /* Ignore non-characters */ + if (((int)mpArgs->KeyChar) < 32 || ((int)mpArgs->KeyChar) > 126) + return; + + /* Check for the numeric-only flag */ + nFieldStart = this->FindStartOfField(m_nCursorPos); + if (nFieldStart > -1 && m_mpCharBuffer[nFieldStart]->NumericOnly && + (((int)mpArgs->KeyChar) < 48 || ((int)mpArgs->KeyChar) > 57)) + return; + + /* Everything else */ + if (!mpChar->StartField && !this->IsProtectedField(m_nCursorPos)) { + /* Write the character to the buffer */ + m_mpCharBuffer[m_nCursorPos]->Character = (wchar_t)mpArgs->KeyChar; + if (nFieldStart != -1) + m_mpCharBuffer[nFieldStart]->Modified = true; + this->MoveCursor(1); + } +} + +/*********************************************************** + Builds an array of modified fields for transmission to + the mainframe. + + @param mpData the input data array +***********************************************************/ +array^ CClient3270::MergeModifiedFields(array^ mpData) +{ + bool fInField = false; + bool fCopy = false; + + for (int i = 0; i < m_mpCharBuffer->Length; i++) { + /* Mark when we hit a field */ + if (m_mpCharBuffer[i]->StartField) + fInField = true; + + /* Look for modified fields */ + if (m_mpCharBuffer[i]->StartField && m_mpCharBuffer[i]->Modified) { + array::Resize(mpData, mpData->Length + 3); + mpData[mpData->Length - 3] = IBM3270DS_ORDER_SBA; + mpData[mpData->Length - 2] = m_mpAddrTbl[((i + 1) >> 6) & 0x3F]; + mpData[mpData->Length - 1] = m_mpAddrTbl[(i + 1) & 0x3F]; + fCopy = true; + + if (fInField) + continue; + } + + /* If we're not in a field, then copy the character, otherwise + only copy when the copy flag is set. */ + if (fInField && !fCopy) + continue; + + /* Skip null characters */ + if (m_mpCharBuffer[i]->Character == NULL) { + fCopy = false; + continue; + } + + array::Resize(mpData, mpData->Length + 1); + mpData[mpData->Length - 1] = + CEbcdicTranslator::CharToEBCDIC(m_mpCharBuffer[i]->Character); + } + + return mpData; +} + +/*********************************************************** + Sets the cursor position. + + @param nStep the increment value +***********************************************************/ +void CClient3270::MoveCursor(int nStep) +{ + /* Move the cursor */ + int nNewPos = m_nCursorPos + nStep; + if (nNewPos < 0) + nNewPos = m_mpCharBuffer->Length - 1; + if (nNewPos >= m_mpCharBuffer->Length) + nNewPos = 0; + + /* Find the coordinates */ + int nRow = (int)Math::Floor(nNewPos / this->Size.Width); + int nCol = nNewPos - (nRow * this->Size.Width); + + /* Paint the cursor */ + this->MoveCursor(nCol, nRow); +} + +/*********************************************************** + Sets the cursor position. + + @param x the x coordinate (abscissa) + @param y the y coordinate (ordinate) +***********************************************************/ +void CClient3270::MoveCursor(int x, int y) +{ + this->MoveCursor(x, y, false); +} + +/*********************************************************** + Sets the cursor position. + + @param x the x coordinate (abscissa) + @param y the y coordinate (ordinate) + @param fRelative the flag to indicate relative position +***********************************************************/ +void CClient3270::MoveCursor(int x, int y, bool fRelative) +{ + if (fRelative) { + int nRow = (int)Math::Floor( m_nCursorPos / this->Size.Width); + int nCol = m_nCursorPos - (nRow * this->Size.Width); + + x += nCol; + if (x < 0) + x += this->Size.Width; + if (x >= this->Size.Width) + x -= this->Size.Width; + + y += nRow; + if (y < 0) + y += this->Size.Height; + if (y >= this->Size.Height) + y -= this->Size.Height; + } + + int nOldPos = m_nCursorPos; + m_nCursorPos = (y * this->Size.Width) + x; + + this->RepaintScreen(nOldPos, nOldPos, false); + this->RepaintScreen(m_nCursorPos, m_nCursorPos, false); +} + +/*********************************************************** + Interprets the 3270 data stream. + + @param mpData -- the input buffer + @param nLength -- the length of the stream +***********************************************************/ +void CClient3270::ParseStream(array^ mpData, int nLength) +{ + /* Is there anything to do? */ + if (mpData->Length == 0) + return; + + /* Process the 3270 command */ + switch (mpData[0]) { + + /* Erase/Write */ + case IBM3270DS_CMD_EW: + case IBM3270DS_CMD_EW_EBCDIC: + this->EraseScreen(); + + /* Write */ + case IBM3270DS_CMD_W: + case IBM3270DS_CMD_W_EBCDIC: + if (!this->UnserializeWCC(mpData, 1)) + break; + this->WriteBuffer(mpData, 2, mpData->Length - 2); + + break; + + /* Write Structured Field */ + case IBM3270DS_CMD_WSF: + case IBM3270DS_CMD_WSF_EBCDIC: + this->WriteStructuredField(mpData, 1); + break; + + /* No Operation */ + case IBM3270DS_CMD_NOP: + break; + } +} + +/*********************************************************** + Handles a read request for the given screen partition. + + @param nPartId id of the partition to read + @param nOpCode the read operation code +***********************************************************/ +void CClient3270::ReadPartition(int nPartId, int nOpCode) +{ + switch (nOpCode) { + + /* Query */ + case IBM3270DS_SF_RP_QUERY: + /* The partition must be FF */ + if (nPartId != 0xFF) + break; + + /* We don't support anything :( */ + array^ mpReply = { + IBM3270DS_AID_SF, + 0x00, /* Length byte */ + 0x04, /* Length byte */ + IBM3270DS_SF_QUERY_REPLY, + IBM3270DS_SF_QR_NULL + }; + m_mpClient->Send(mpReply); + break; + } +} + +/*********************************************************** + The callback function for receiving data. + + @param mpData the data received +***********************************************************/ +void CClient3270::ReceiveCallback(array^ mpData) +{ + this->ParseStream(mpData, mpData->Length); +} + +/*********************************************************** + Repeats a character until given address. + + @param mpData the data received +***********************************************************/ +void CClient3270::RepeatToAddress(Byte chChar, int nStopAddr) +{ + bool fWrap = (nStopAddr <= m_nBufferPos); + + for (int i = m_nBufferPos; i < m_mpCharBuffer->Length; i++) { + /* Write to the character buffer */ + C3270Char^ mpChar = gcnew C3270Char(chChar); + m_mpCharBuffer[i] = mpChar; + m_nBufferPos = i + 1; + + /* Wrap to the top of the buffer */ + if (i + 1 >= m_mpCharBuffer->Length && fWrap) { + m_nBufferPos = 0; + i = 0; + } + + /* Break out if we're at the stop address */ + if ((nStopAddr > 0 && i == nStopAddr - 1) || (nStopAddr == 0 && i == 0)) + break; + } +} + +/*********************************************************** + Resets the modified flag on all characters in the buffer. +***********************************************************/ +void CClient3270::ResetAllFieldMDTs() +{ + for (int i = 0; i < m_mpCharBuffer->Length; i++) + m_mpCharBuffer[i]->Modified = false; +} + +/*********************************************************** + Draws the character buffer on the canvas. +***********************************************************/ +void CClient3270::RepaintScreen() +{ + this->RepaintScreen(0, m_mpCharBuffer->Length - 1, true); +} + +/*********************************************************** + Draws the character buffer on the canvas. + + @param nStartPos the starting position of the pen + @param nEndPos the last position to redraw + @param fErase flag to erase the canvas +***********************************************************/ +void CClient3270::RepaintScreen(int nStartPos, int nEndPos, bool fErase) +{ + Brush^ mpBrush; + int nFieldStart; + bool fMasked; + + /* Bounds check */ + if (nStartPos < 0 || nEndPos >= m_mpCharBuffer->Length) + return; + + /* Notify the client that we're repainting the screen */ + if (m_mpOnPaintCbk != nullptr) + m_mpOnPaintCbk->Invoke(nStartPos, nEndPos, fErase); + + if (m_pnlCanvas == nullptr) + return; + + Graphics^ mpCanvas = m_pnlCanvas->CreateGraphics(); + if (fErase) + mpCanvas->Clear(m_pnlCanvas->BackColor); + + /* Calculate block size */ + float nColWidth = + (float)m_pnlCanvas->Bounds.Width / (this->Size.Width + 5); + float nRowHeight = + (float)m_pnlCanvas->Bounds.Height / (this->Size.Height + 1); + + /* Calculate the font size */ + Font^ mpFont = gcnew Font(FontFamily::GenericMonospace, 24); + SizeF^ mpFontSize = mpCanvas->MeasureString("W", mpFont); + float nNewFontSize = (24 * (nColWidth / mpFontSize->Width)) + 2; + mpFont = gcnew Font(mpFont->FontFamily, nNewFontSize); + + /* Draw each character on the canvas */ + for (int i = nStartPos; i <= nEndPos; i++) { + /* Get the current block */ + int nRow = (int)Math::Floor(i / this->Size.Width); + int nCol = i - (nRow * this->Size.Width); + RectangleF^ mpBlock = gcnew RectangleF( + (float) nCol * nColWidth, + (float) nRow * nRowHeight, + (float) nColWidth, + (float) nRowHeight + ); + + /* Draw the cursor */ + if (i == m_nCursorPos) + mpCanvas->FillRectangle(Brushes::Gray, *mpBlock); + else + mpCanvas->FillRectangle(Brushes::Black, *mpBlock); + + /* Get the brush */ + if (m_mpCharBuffer[i]->StartField) { + mpBrush = m_mpCharBuffer[i]->GetPaintBrush(m_fBaseColor); + fMasked = false; + } else { + nFieldStart = this->FindStartOfField(i); + mpBrush = (nFieldStart == -1 ? Brushes::Green : + m_mpCharBuffer[nFieldStart]->GetPaintBrush(m_fBaseColor)); + fMasked = (nFieldStart == -1 ? false : + !m_mpCharBuffer[nFieldStart]->Protected && + m_mpCharBuffer[nFieldStart]->Display == C3270Char::DisplayOptions::Dark); + } + + /* And finally, draw the character */ + mpCanvas->DrawString( + m_mpCharBuffer[i]->ToString(fMasked), + mpFont, + mpBrush, + *mpBlock + ); + } +} + +/*********************************************************** + The callback function for sending data. +***********************************************************/ +void CClient3270::SendCallback() +{ + +} + +/*********************************************************** + Unserializes the flags in the write control character. + + @param mpData the data buffer to examine + @param nOffset the offset position to start reading + + @return TRUE for WCC found, FALSE otherwise +***********************************************************/ +bool CClient3270::UnserializeWCC(array^ mpData, int nOffset) +{ + /* Check for WCC */ + if (nOffset + 1 >= mpData->Length) + return false; + + Byte chWCC = mpData[nOffset]; + + /* Bit 7: Reset MDT */ + if (chWCC & 0x01) + this->ResetAllFieldMDTs(); + + /* Bit 6: Unlock Keyboard */ + if (chWCC >> 1 & 0x01) + m_fKeybLocked = false; + + /* Bit 5: Sound Alarm */ + m_fSoundAlaram = (chWCC >> 2 & 0x01); + + /* Bit 4: Start Printer */ + m_fStartPrinter = (chWCC >> 3 & 0x01); + + /* Bits 2-3: Print Line Length */ + if ((chWCC >> 4 & 0x03) == 0x00) + m_nLineLength = 132; + else if ((chWCC >> 4 & 0x03) == 0x01) + m_nLineLength = 40; + else if ((chWCC >> 4 & 0x03) == 0x02) + m_nLineLength = 64; + else if ((chWCC >> 4 & 0x03) == 0x03) + m_nLineLength = 80; + + /* Bit 1: WCC Reset (Ignored) */ + + /* Bit 0: Unknown (Ignored) */ + + return true; +} + +/*********************************************************** + Writes data to the character buffer. + + @param mpData the data buffer to examine + @param nOffset the offset position to start reading + @param nLength the length of the data to write +***********************************************************/ +void CClient3270::WriteBuffer(array^ mpData, int nOffset, int nLength) +{ + C3270Char^ mpChar; + + for (int i = nOffset; i < mpData->Length; i++) { + /* Make sure we don't exceed the data length */ + if (i >= nLength) + break; + + /* Get the order and adjust the position accordingly */ + i = this->InterpretOrder(mpData, i); + if (i >= mpData->Length) + break; + + /* Write to the character buffer */ + mpChar = gcnew C3270Char(mpData[i]); + m_mpCharBuffer[m_nBufferPos] = mpChar; + m_nBufferPos++; + + /* Wrap so we don't go off the screen */ + if (m_nBufferPos >= m_mpCharBuffer->Length) + m_nBufferPos = 0; + } + + this->RepaintScreen(); +} + +/*********************************************************** + Writes an output structured field. + + @param mpData the data buffer to examine + @param nOffset the offset position to start reading +***********************************************************/ +void CClient3270::WriteStructuredField(array^ mpData, int nOffset) +{ + int nPId; + int nType; + + for (int i = nOffset; i < mpData->Length; i++) { + /* Get the field length */ + if (i + 1 >= mpData->Length) + break; + int nLength = (mpData[i] << 8) + mpData[i + 1]; + + /* Check for an invalid field length */ + if (nLength > 0 && nLength < 3) + break; + + /* Get the structured field id (one byte first) */ + int nSFId = mpData[i + 2]; + + switch (nSFId) { + + /* Read Partition */ + case IBM3270DS_SF_READ_PART: + /* Bounds check */ + if (nLength < 5 || (i + 5) > mpData->Length) + break; + + /* Get the partition id and type id */ + nPId = mpData[i + 3]; + nType = mpData[i + 4]; + + this->ReadPartition(nPId, nType); + break; + + default: + /* Unsupported SFID */ + i += nLength; + continue; + } + } +} diff --git a/CClient3270.h b/CClient3270.h new file mode 100644 index 0000000..cbcf14c --- /dev/null +++ b/CClient3270.h @@ -0,0 +1,194 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#pragma once + +#include "CTelnetClient.h" +#include "C3270Char.h" + +namespace WinTN3270 +{ + /*********************************************************** + Telnet client for IBM 3270. + ***********************************************************/ + public ref class CClient3270 + { + + public: /* Public Delegates */ + typedef CTelnetClient::OnCertPolicyError OnCertPolicyError; + + private: /* Buffer Address Conversion Table */ + static cli::array^ m_mpAddrTbl = + { 0x40, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, + 0xD8, 0xD9, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F }; + + public: // Public Delegates + delegate void OnPaintEvent(int nStartPos, int nEndPos, bool fErase); + + private: // Private Member Attributes + bool m_fBaseColor; + bool m_fKeybLocked; + bool m_fSoundAlaram; + bool m_fStartPrinter; + CTelnetClient^ m_mpClient; + cli::array^ m_mpCharBuffer; + OnPaintEvent^ m_mpOnPaintCbk; + int m_nActionId; + int m_nBufferPos; + int m_nCursorPos; + int m_nCols; + int m_nLastField; + int m_nLineLength; + int m_nModel; + int m_nRows; + System::Windows::Forms::PictureBox^ m_pnlCanvas; + + public: // Public Properties + property bool BaseColorMode + { + bool get() + { + return m_fBaseColor; + } + + void set(bool fValue) + { + m_fBaseColor = fValue; + } + } + + property cli::array^ Buffer + { + cli::array^ get() { return m_mpCharBuffer; } + } + + property System::Windows::Forms::PictureBox^ Canvas + { + System::Windows::Forms::PictureBox^ get() + { + return m_pnlCanvas; + } + + void set(System::Windows::Forms::PictureBox^ pnlCanvas) + { + m_pnlCanvas = pnlCanvas; + } + } + + property OnCertPolicyError^ CertificateErrorCallback + { + OnCertPolicyError^ get() + { + return m_mpClient->CertificateErrorCallback; + } + + void set(OnCertPolicyError^ mpValue) + { + m_mpClient->CertificateErrorCallback = mpValue; + } + } + + property bool Connected + { + bool get() { return m_mpClient->Connected; } + } + + property bool Locked + { + bool get() { return m_fKeybLocked; } + } + + property int Model + { + int get() { return m_nModel; } + } + + property OnPaintEvent^ PaintCallback + { + OnPaintEvent^ get() + { + return m_mpOnPaintCbk; + } + + void set(OnPaintEvent^ mpValue) + { + m_mpOnPaintCbk = mpValue; + } + } + + property System::Drawing::Size Size + { + System::Drawing::Size get() + { + System::Drawing::Size oSize; + oSize.Height = m_nRows; + oSize.Width = m_nCols; + + return oSize; + } + } + + private: /* Private Member Functions */ + void __Constructor(System::String^ cchIPAddr, int nPort, int nModel); + int ConvertBufferAddress(System::Byte chFirst, System::Byte chSecond); + cli::array^ CreateActionArray(System::Byte chActionId); + bool DoInputAction(wchar_t chKeyChar); + void EraseAllUnprotected(); + void EraseScreen(); + int FindStartOfField(int nOffset); + int InterpretOrder(cli::array^ mpData, int nOffset); + bool IsProtectedField(int nOffset); + void JumpToNextUnprotectedField(); + void JumpToPrevUnprotectedField(); + cli::array^ MergeModifiedFields(cli::array^ mpData); + void MoveCursor(int nStep); + void ParseStream(cli::array^ mpData, int nLength); + void ReadPartition(int nPartId, int nOpCode); + void ReceiveCallback(cli::array^ mpData); + void RepaintScreen(int nStartPos, int nEndPos, bool fErase); + void RepeatToAddress(System::Byte chChar, int nStopAddr); + void ResetAllFieldMDTs(); + void SendCallback(); + bool UnserializeWCC(cli::array^ mpData, int nOffset); + void WriteBuffer(cli::array^ mpData, int nOffset, int nLength); + void WriteStructuredField(cli::array^ mpData, int nOffset); + + public: /* Public Member Functions */ + CClient3270(System::String^ cchIPAddr, int nPort); + CClient3270(System::String^ cchIPAddr, int nPort, int nModel); + ~CClient3270(); + void Connect(); + void Connect(bool fSecure); + void Disconnect(); + void KeyPress(System::Windows::Forms::KeyPressEventArgs^ mpArgs); + void KeyPress(System::Windows::Forms::KeyPressEventArgs^ mpArgs, + System::Windows::Forms::Keys nModifiers); + void MoveCursor(int x, int y); + void MoveCursor(int x, int y, bool fRelative); + void RepaintScreen(); + }; +} diff --git a/CEbcdicTranslator.cpp b/CEbcdicTranslator.cpp new file mode 100644 index 0000000..1048991 --- /dev/null +++ b/CEbcdicTranslator.cpp @@ -0,0 +1,86 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#include "stdafx.h" +#include "CEbcdicTranslator.h" + +using namespace WinTN3270; +using namespace System; + +/*********************************************************** + Translates a byte from EBCDIC to ASCII. + + @param mpInput the input byte array + + @return an ASCII-encoded character array +***********************************************************/ +wchar_t CEbcdicTranslator::CharToASCII(Byte chInput) +{ + return (wchar_t)m_mpEbc2AscTbl[(int) chInput]; +} + +/*********************************************************** + Translates a byte from ASCII to EBCDIC. + + @param pchInput the input character array + + @return an EBCDIC-encoded byte array +***********************************************************/ +Byte CEbcdicTranslator::CharToEBCDIC(wchar_t chInput) +{ + return (Byte)m_mpAsc2EbcTbl[(int) chInput]; +} + +/*********************************************************** + Translates bytes from EBCDIC to ASCII. + + @param mpInput the input byte array + + @return an ASCII-encoded character array +***********************************************************/ +array^ CEbcdicTranslator::StringToASCII(array^ mpInput) +{ + array^ pchOutput = gcnew array(mpInput->Length); + + /* Convert characters using the EBCDIC-to-ASCII table */ + for (int i = 0; i < mpInput->Length; i++) + pchOutput[i] = CEbcdicTranslator::CharToASCII(mpInput[i]); + + return pchOutput; +} + +/*********************************************************** + Translates bytes from ASCII to EBCDIC. + + @param pchInput the input character array + + @return an EBCDIC-encoded byte array +***********************************************************/ +array^ CEbcdicTranslator::StringToEBCDIC(array^ pchInput) +{ + array^ mpOutput = gcnew array(pchInput->Length); + + /* Convert characters using the ASCII-to-EBCDIC table */ + for (int i = 0; i < pchInput->Length; i++) + mpOutput[i] = CEbcdicTranslator::CharToEBCDIC(pchInput[i]); + + return mpOutput; +} diff --git a/CEbcdicTranslator.h b/CEbcdicTranslator.h new file mode 100644 index 0000000..a13856f --- /dev/null +++ b/CEbcdicTranslator.h @@ -0,0 +1,95 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#pragma once + +namespace WinTN3270 +{ + /*********************************************************** + Translates strings between ASCII and EBCDIC. + ***********************************************************/ + public ref class CEbcdicTranslator + { + + private: /* ASCII-EBCDIC Conversion Tables */ + static cli::array^ m_mpAsc2EbcTbl = + { 0x00, 0x01, 0x02, 0x03, 0x37, 0x2D, 0x2E, 0x2F, 0x16, 0x05, + 0x25, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, + 0x3C, 0x3D, 0x32, 0x26, 0x18, 0x19, 0x3F, 0x27, 0x1C, 0x1D, + 0x1E, 0x1F, 0x40, 0x5A, 0x7F, 0x7B, 0x5B, 0x6C, 0x50, 0x7D, + 0x4D, 0x5D, 0x5C, 0x4E, 0x6B, 0x60, 0x4B, 0x61, 0xF0, 0xF1, + 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0x7A, 0x5E, + 0x4C, 0x7E, 0x6E, 0x6F, 0x7C, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0xC8, 0xC9, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, + 0xD7, 0xD8, 0xD9, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, + 0xE9, 0xAD, 0xE0, 0xBD, 0x5F, 0x6D, 0x79, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, + 0x95, 0x96, 0x97, 0x98, 0x99, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, + 0xA7, 0xA8, 0xA9, 0xC0, 0x4F, 0xD0, 0xA1, 0x07, 0x00, 0x01, + 0x02, 0x03, 0x37, 0x2D, 0x2E, 0x2F, 0x16, 0x05, 0x25, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x3C, 0x3D, + 0x32, 0x26, 0x18, 0x19, 0x3F, 0x27, 0x1C, 0x1D, 0x1E, 0x1F, + 0x40, 0x5A, 0x7F, 0x7B, 0x5B, 0x6C, 0x50, 0x7D, 0x4D, 0x5D, + 0x5C, 0x4E, 0x6B, 0x60, 0x4B, 0x61, 0xF0, 0xF1, 0xF2, 0xF3, + 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0x7A, 0x5E, 0x4C, 0x7E, + 0x6E, 0x6F, 0x7C, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, + 0xD9, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xAD, + 0xE0, 0xBD, 0x5F, 0x6D, 0x79, 0x81, 0x82, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0x0, 0x4F, 0xD0, 0xA1, 0x07 }; + + static cli::array^ m_mpEbc2AscTbl = + { 0x00, 0x01, 0x02, 0x03, 0x00, 0x09, 0x00, 0x7F, 0x00, 0x00, + 0x00, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, + 0x00, 0x0A, 0x0B, 0x00, 0x18, 0x19, 0x00, 0x00, 0x1C, 0x1D, + 0x1E, 0x1F, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x0A, 0x17, 0x1B, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x06, 0x07, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x15, 0x00, 0x1A, 0x20, 0x00, 0xE2, 0xE4, 0xE0, 0xE1, + 0xE3, 0xE5, 0xE7, 0xF0, 0xA2, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, + 0x26, 0xE9, 0xEA, 0xEB, 0xE8, 0xED, 0xEE, 0xEF, 0xEC, 0xDF, + 0x21, 0x24, 0x2A, 0x29, 0x3B, 0x5E, 0x2D, 0x2F, 0xC2, 0xC4, + 0xC0, 0xC1, 0xC3, 0xC5, 0xC7, 0xD1, 0xA6, 0x2C, 0x25, 0x5F, + 0x3E, 0x3F, 0xF8, 0xC9, 0xCA, 0xCB, 0xC8, 0xCD, 0xCE, 0xCF, + 0xCC, 0x5F, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, 0xD8, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0xAB, 0xBB, + 0xF0, 0x0D, 0xDE, 0xB1, 0xB0, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, + 0x6F, 0x70, 0x71, 0x72, 0xAA, 0xBA, 0xE6, 0xB8, 0xC6, 0xA4, + 0xB5, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, + 0xA1, 0xBF, 0xD0, 0x5B, 0xFE, 0xAE, 0xAC, 0xA3, 0xA5, 0x95, + 0xA9, 0xA7, 0xB6, 0xBC, 0xBD, 0xBE, 0xDD, 0xA8, 0xAF, 0x5D, + 0x92, 0xD7, 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x9B, 0xF4, 0xF6, 0xF2, 0xF3, 0xF5, 0x7D, 0x4A, + 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0xB9, 0xFB, + 0xFC, 0xF9, 0xFA, 0xFF, 0xFC, 0xF7, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5A, 0xB2, 0xD4, 0xD6, 0xD2, 0xD3, 0xD5, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0xB3, 0xDB, 0xDC, 0xD9, 0xDA, 0x00 }; + + public: /* Public Member Functions */ + static wchar_t CharToASCII(System::Byte chInput); + static System::Byte CharToEBCDIC(wchar_t chInput); + static cli::array^ StringToASCII(cli::array^ mpInput); + static cli::array^ StringToEBCDIC(cli::array^ pchInput); + }; +} \ No newline at end of file diff --git a/CTelnetClient.cpp b/CTelnetClient.cpp new file mode 100644 index 0000000..47aa36f --- /dev/null +++ b/CTelnetClient.cpp @@ -0,0 +1,806 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#include "stdafx.h" +#include "CTelnetClient.h" + +using namespace WinTN3270; +using namespace System; +using namespace System::Net; +using namespace System::Net::Security; +using namespace System::Net::Sockets; +using namespace System::Security::Authentication; +using namespace System::Security::Cryptography::X509Certificates; +using namespace System::Threading; +using namespace System::Collections; + +/* The length of this delegate is absurd */ +typedef RemoteCertificateValidationCallback RemoteCertValidateCbk; + +/*********************************************************** + Creates a new TELNET client. + + @param cchIPAddr the IP address of the remote host + @param nPort the remote TCP port +***********************************************************/ +CTelnetClient::CTelnetClient(String^ cchIPAddr, int nPort) +{ + /* Setup the stream socket */ + m_mpSocket = gcnew Socket( + AddressFamily::InterNetwork, + SocketType::Stream, + ProtocolType::Tcp + ); + IPHostEntry^ mpRemoteHost = Dns::GetHostEntry(cchIPAddr); + if (mpRemoteHost->AddressList->Length == 0) + throw gcnew Exception("No IP addresses are associated with the host record."); + m_mpRemoteIP = gcnew IPEndPoint(mpRemoteHost->AddressList[0], nPort); + + /* Initialize stuff */ + m_mpRcvCommandBuffer = gcnew array(0); + m_mpRcvDataBuffer = gcnew array(0); + m_mpTransmQueue = gcnew array(0); + m_mpServerOpts = gcnew Hashtable(); + m_mpClientOpts = gcnew Hashtable(); + m_cchRemoteHost = cchIPAddr; + m_fGoAhead = true; + + m_mpSendEvent = gcnew EventWaitHandle(true, EventResetMode::ManualReset); + + /* Set default options */ + this->FullDuplex = true; +} + +/*********************************************************** + Disconnects from the remote host. +***********************************************************/ +CTelnetClient::~CTelnetClient() +{ + this->Disconnect(); +} + +/*********************************************************** + Callback for validating SSL certificates. + + @param mpSender the calling object + @param mpCertificate the cert to validate + @param mpChain the certificate chain + @param ePolicyErrors any policy errors + + @return TRUE for valid, FALSE otherwise +***********************************************************/ +bool CTelnetClient::CertValidationCallback(Object^ mpSender, + X509Certificate^ mpCertificate, X509Chain^ mpChain, + SslPolicyErrors ePolicyErrors) +{ + /* Accept all certs without policy errors */ + if (ePolicyErrors == SslPolicyErrors::None) + return true; + + /* Get the callback function */ + Monitor::Enter(this); + OnCertPolicyError^ mpCertErrorCbk = m_mpCertErrorCbk; + Monitor::Exit(this); + + /* If we have no policy error handler, then don't accept */ + if (mpCertErrorCbk == nullptr) + return false; + + /* Otherwise let the handler decide */ + return mpCertErrorCbk->Invoke( + mpCertificate, + mpChain, + ePolicyErrors + ); +} + +/*********************************************************** + Opens a new connection to the remote host. +***********************************************************/ +void CTelnetClient::Connect() +{ + this->Connect(false); +} + +/*********************************************************** + Opens a new connection to the remote host. + + @param fSecure enable SSL +***********************************************************/ +void CTelnetClient::Connect(bool fSecure) +{ + /* Make sure we're not already connected */ + if (m_mpSocket->Connected) + return; + + /* Connect to the remote host */ + if (m_mpRemoteIP == nullptr) + throw gcnew Exception("Remote end-point is not set."); + m_mpSocket->Connect(m_mpRemoteIP); + m_mpSocketStream = gcnew NetworkStream(m_mpSocket); + + /* If SSL is enabled, then wrap the stream */ + if (fSecure) { + m_mpSocketStream = gcnew SslStream( + m_mpSocketStream, + false, + gcnew RemoteCertValidateCbk(this, &CTelnetClient::CertValidationCallback) + ); + + try { + ((SslStream^) m_mpSocketStream)->AuthenticateAsClient(m_cchRemoteHost); + } catch(AuthenticationException^ e) { + m_mpSocket->Disconnect(true); + delete e; + + return; + } + } + + /* Create the new state struct */ + SOCKETRCVSTATE^ mpRcvState = gcnew SOCKETRCVSTATE; + mpRcvState->mpBuffer = gcnew array(256); + mpRcvState->mpAscCallback = m_mpReceiveAscCbk; + mpRcvState->mpBinCallback = m_mpReceiveBinCbk; + mpRcvState->mpClient = this; + mpRcvState->mpStream = m_mpSocketStream; + + /* Start receving data */ + m_mpSocketStream->BeginRead( + mpRcvState->mpBuffer, + 0, + mpRcvState->mpBuffer->Length, + gcnew AsyncCallback(&CTelnetClient::ReceiveProc), + mpRcvState + ); +} + +/*********************************************************** + Terminates the active stream connection. +***********************************************************/ +void CTelnetClient::Disconnect() +{ + if (!m_mpSocket->Connected) + return; + + m_mpSocket->Disconnect(true); +} + +/*********************************************************** + Send all pending transmit data to the remote host. +***********************************************************/ +void CTelnetClient::FlushTransmitQueue() +{ + /* Copy to our buffer and clear the queue */ + Monitor::Enter(this); + array^ mpData = gcnew array(m_mpTransmQueue->Length); + array::Copy(m_mpTransmQueue, mpData, mpData->Length); + m_mpTransmQueue = gcnew array(0); + Monitor::Exit(this); + + /* We should always have something to send, but... */ + if (mpData->Length == 0) + return; + + /* Reset the 'go ahead' flag if we're on half-duplex */ + if (!this->FullDuplex) + m_fGoAhead = false; + + SOCKETSNDSTATE^ mpSndState = gcnew SOCKETSNDSTATE; + mpSndState->mpCallback = m_mpSendCbk; + mpSndState->mpClient = this; + mpSndState->mpStream = m_mpSocketStream; + + /* SslStream does not allow concurrent asynchronous writes, so we have to wait + for the last write to finish. */ + m_mpSendEvent->WaitOne(); + m_mpSendEvent->Reset(); + + try { + m_mpSocketStream->BeginWrite( + mpData, + 0, + mpData->Length, + gcnew AsyncCallback(&CTelnetClient::SendProc), + mpSndState + ); + } catch (SocketException^ e) { + throw e; + } + + if (mpSndState->mpCallback != nullptr) + mpSndState->mpCallback->Invoke(); +} + +/*********************************************************** + Gets existing option values for the client. + + @param nId the option id + + @return a byte array containg the value +***********************************************************/ +array^ CTelnetClient::GetClientOption(int nId) +{ + /* Detect if the option isn't set */ + if (!m_mpClientOpts->ContainsKey(nId)) + return nullptr; + + try { + /* First try a direct cast */ + return (array^)m_mpClientOpts[nId]; + } catch (InvalidCastException^ e) { + delete e; + } + + try { + /* Maybe it's a boolean? */ + return (array^)BitConverter::GetBytes( (bool) m_mpClientOpts[nId]); + } catch (InvalidCastException^ e) { + delete e; + } + + /* This should never happen */ + return gcnew array(0); +} + +/*********************************************************** + Gets existing option values for the server. + + @param nId the option id + + @return a byte array containg the value +***********************************************************/ +array^ CTelnetClient::GetServerOption(int nId) +{ + /* Detect if the option isn't set */ + if (!m_mpServerOpts->ContainsKey(nId)) + return nullptr; + + try { + /* First try a direct cast */ + return (array^)m_mpServerOpts[nId]; + } catch (InvalidCastException^ e) { + delete e; + } + + try { + /* Maybe it's a boolean? */ + return (array^)BitConverter::GetBytes((bool)m_mpServerOpts[nId]); + } catch (InvalidCastException^ e) { + delete e; + } + + /* This should never happen */ + return gcnew array(0); +} + +/*********************************************************** + Interprets and handles TELNET commands. + + @param mpData the receive data buffer + + @return the sanitized array +***********************************************************/ +array^ CTelnetClient::InterpretCommands(array^ mpData) +{ + bool fNoDataFlush = (m_mpRcvDataBuffer->Length != 0); + bool fNoCommandFlush = false; + array^ mpNewDataBuffer; + array^ mpResp; + bool fResult; + + /* Merge the incoming data with the buffer */ + int nStartIndex = m_mpRcvCommandBuffer->Length; + if (mpData != nullptr) { + array::Resize( + m_mpRcvCommandBuffer, + m_mpRcvCommandBuffer->Length + mpData->Length + ); + array::Copy(mpData, 0, m_mpRcvCommandBuffer, nStartIndex, mpData->Length); + } + + if (m_mpRcvCommandBuffer->Length == 0) + return nullptr; + + for (int i = 0; i < m_mpRcvCommandBuffer->Length; i++) { + nStartIndex = i; + + /* Check for the end-of-record flag */ + fNoDataFlush = this->TestServerOption(TELNET_OPTION_END_OF_RECORD); + + /* There should never be a single FF character at the end + of the buffer. So if we see one, wait for more data. */ + if (m_mpRcvCommandBuffer[i] == TELNET_COMMAND_IAC && + i == m_mpRcvCommandBuffer->Length - 1) + { + fNoCommandFlush = true; + break; + } + + /* If we're in sub-option negotiation and we don't + see an IAC, then capture the option value */ + if ((m_mpSubOpt != nullptr && m_mpRcvCommandBuffer[i] != TELNET_COMMAND_IAC) || + (m_mpSubOpt != nullptr && m_mpRcvCommandBuffer[i] == TELNET_COMMAND_IAC + && m_mpRcvCommandBuffer[i + 1] == TELNET_COMMAND_IAC)) + { + array::Resize( + m_mpSubOpt->pchValue, + m_mpSubOpt->pchValue->Length + 1 + ); + m_mpSubOpt->pchValue[m_mpSubOpt->pchValue->Length - 1] = m_mpRcvCommandBuffer[i]; + + if (m_mpRcvCommandBuffer[i] == TELNET_COMMAND_IAC) + i++; + + continue; + } + + /* Two IAC bytes in a row indicate one FF character, + so it's only a command if we see one. */ + if (m_mpRcvCommandBuffer[i] != TELNET_COMMAND_IAC || + (m_mpRcvCommandBuffer[i] == TELNET_COMMAND_IAC && + m_mpRcvCommandBuffer[i + 1] == TELNET_COMMAND_IAC)) + { + array::Resize( + m_mpRcvDataBuffer, + m_mpRcvDataBuffer->Length + 1 + ); + m_mpRcvDataBuffer[m_mpRcvDataBuffer->Length - 1] = m_mpRcvCommandBuffer[i]; + + if (m_mpRcvCommandBuffer[i] == TELNET_COMMAND_IAC) + i++; + + continue; + } + + /* If we're at the end of the loop, then buffer the command */ + if (i == m_mpRcvCommandBuffer->Length - 1) { + fNoCommandFlush = true; + break; + } + + /* Process the given command per RFC0854 */ + switch (m_mpRcvCommandBuffer[i + 1]) { + + /* End of Record */ + case (TELNET_COMMAND_END_OF_RECORD): + /* Flush the data buffer */ + mpNewDataBuffer = gcnew array(m_mpRcvDataBuffer->Length); + array::Copy( + m_mpRcvDataBuffer, + mpNewDataBuffer, + m_mpRcvDataBuffer->Length + ); + array::Resize(m_mpRcvDataBuffer, 0); + + /* Exit and wait for more data */ + fNoCommandFlush = true; + nStartIndex = (i + 2); + break; + + /* End sub-option negotiation */ + case (TELNET_COMMAND_SE): + /* Get the option value */ + if (m_mpSubOpt->fValueRequired) + m_mpSubOpt->pchValue = this->GetClientOption(m_mpSubOpt->nOptionId); + + /* Send the reply */ + mpResp = gcnew array(m_mpSubOpt->pchValue->Length + 6); + mpResp[0] = TELNET_COMMAND_IAC; + mpResp[1] = TELNET_COMMAND_SB; + mpResp[2] = m_mpSubOpt->nOptionId; + mpResp[3] = 0x00; + array::Copy( + m_mpSubOpt->pchValue, + 0, + mpResp, + 4, + m_mpSubOpt->pchValue->Length + ); + mpResp[m_mpSubOpt->pchValue->Length + 4 + 0] = TELNET_COMMAND_IAC; + mpResp[m_mpSubOpt->pchValue->Length + 4 + 1] = TELNET_COMMAND_SE; + this->Transmit(mpResp); + + m_mpSubOpt = nullptr; + i++; + break; + + /* No operation */ + case (TELNET_COMMAND_NOP): + i += 2; + break; + + /* Are you there? */ + case (TELNET_COMMAND_ARE_YOU_THERE): + mpResp = gcnew array(2); + mpResp[0] = TELNET_COMMAND_IAC; + mpResp[1] = TELNET_COMMAND_NOP; + this->Transmit(mpResp); + i += 2; + break; + + /* Go Ahead */ + case (TELNET_COMMAND_GA): + m_fGoAhead = true; + this->FlushTransmitQueue(); + i += 2; + break; + + /* Begin sub-option negotiation */ + case (TELNET_COMMAND_SB): + /* Check for all parameters */ + if (i + 3 >= m_mpRcvCommandBuffer->Length) { + fNoCommandFlush = true; + break; + } + + /* We're in a sub-option now */ + m_mpSubOpt = gcnew TNSUBOPTION; + m_mpSubOpt->nOptionId = m_mpRcvCommandBuffer[i + 2]; + m_mpSubOpt->pchValue = gcnew array(0); + m_mpSubOpt->fValueRequired = BitConverter::ToBoolean( m_mpRcvCommandBuffer, i + 3); + i += 3; + break; + + + /* Client is offering an option */ + case (TELNET_COMMAND_WILL): + /* Check for all parameters */ + if (i + 2 >= m_mpRcvCommandBuffer->Length) { + fNoCommandFlush = true; + break; + } + + /* Get the client option */ + fResult = (this->GetClientOption(m_mpRcvCommandBuffer[i + 2]) != nullptr); + + /* Send the reply */ + mpResp = gcnew array(3); + mpResp[0] = TELNET_COMMAND_IAC; + mpResp[1] = (fResult ? TELNET_COMMAND_DO : TELNET_COMMAND_DONT); + mpResp[2] = m_mpRcvCommandBuffer[i + 2]; + this->Transmit(mpResp); + i += 2; + break; + + /* Client won't do an option */ + case (TELNET_COMMAND_WONT): + /* Check for all parameters */ + if (i + 2 >= m_mpRcvCommandBuffer->Length) { + fNoCommandFlush = true; + break; + } + + /* Attempt to unset the option */ + fResult = this->SetServerOption(m_mpRcvCommandBuffer[i + 2], nullptr); + + // Send the reply + mpResp = gcnew array(3); + mpResp[0] = TELNET_COMMAND_IAC; + mpResp[1] = (!fResult ? TELNET_COMMAND_DO : TELNET_COMMAND_DONT); + mpResp[2] = m_mpRcvCommandBuffer[i + 2]; + this->Transmit(mpResp); + i += 2; + break; + + /* Client wants an option */ + case (TELNET_COMMAND_DO): + /* Check for all parameters */ + if (i + 2 >= m_mpRcvCommandBuffer->Length) { + fNoCommandFlush = true; + break; + } + + /* Attempt to set the option */ + fResult = this->SetServerOption(m_mpRcvCommandBuffer[i + 2], true); + + // Send the reply + mpResp = gcnew array(3); + mpResp[0] = TELNET_COMMAND_IAC; + mpResp[1] = (fResult ? TELNET_COMMAND_WILL : TELNET_COMMAND_WONT); + mpResp[2] = m_mpRcvCommandBuffer[i + 2]; + this->Transmit(mpResp); + i += 2; + break; + + /* Client doesn't want an option */ + case (TELNET_COMMAND_DONT): + /* Check for all parameters */ + if (i + 2 >= m_mpRcvCommandBuffer->Length) { + fNoCommandFlush = true; + break; + } + + /* Attempt to unset the option */ + fResult = this->SetServerOption(m_mpRcvCommandBuffer[i + 2], nullptr); + + // Send the reply + mpResp = gcnew array(3); + mpResp[0] = TELNET_COMMAND_IAC; + mpResp[1] = (!fResult ? TELNET_COMMAND_WILL : TELNET_COMMAND_WONT); + mpResp[2] = m_mpRcvCommandBuffer[i + 2]; + this->Transmit(mpResp); + i += 2; + break; + + /* Unsupported command */ + default: + i++; + } + + if (fNoCommandFlush) + break; + } + + if (fNoCommandFlush) { + /* Shift the remaining data to the start of the buffer + because we're waiting for the complete request to arrive */ + array::Copy( + m_mpRcvCommandBuffer, + nStartIndex, + m_mpRcvCommandBuffer, + 0, + m_mpRcvCommandBuffer->Length - nStartIndex + ); + array::Resize( + m_mpRcvCommandBuffer, + m_mpRcvCommandBuffer->Length - nStartIndex + ); + } else { + /* We're at the end, empty the buffer */ + array::Resize(m_mpRcvCommandBuffer, 0); + } + + if (!fNoDataFlush && m_mpRcvDataBuffer->Length > 0) { + /* Move data from the receive buffer */ + mpNewDataBuffer = gcnew array(m_mpRcvDataBuffer->Length); + array::Copy( + m_mpRcvDataBuffer, + mpNewDataBuffer, + m_mpRcvDataBuffer->Length + ); + array::Resize(m_mpRcvDataBuffer, 0); + } + + return mpNewDataBuffer; +} + +/*********************************************************** + Callback function for asynchronous receiving. +***********************************************************/ +void CTelnetClient::ReceiveProc(IAsyncResult^ mpResult) +{ + /* Finish the receive operation */ + SOCKETRCVSTATE^ mpRcvState = safe_cast(mpResult->AsyncState); + int nBytesRead = mpRcvState->mpStream->EndRead(mpResult); + + /* If we got some data back... */ + if (nBytesRead > 0) { + /* Trim the fat off the new buffer */ + array::Resize(mpRcvState->mpBuffer, nBytesRead); + + while (true) { + /* Process TELNET commands */ + mpRcvState->mpBuffer = mpRcvState->mpClient->InterpretCommands(mpRcvState->mpBuffer); + if (mpRcvState->mpBuffer == nullptr) + break; + + if (mpRcvState->mpBuffer->Length > 0 && mpRcvState->mpClient->BinaryTransmission) { + if (mpRcvState->mpBinCallback != nullptr) + mpRcvState->mpBinCallback->Invoke(mpRcvState->mpBuffer); + } else if (mpRcvState->mpBuffer->Length > 0) { + /* Convert it to a string */ + array^ pchData = gcnew array(mpRcvState->mpBuffer->Length); + array::Copy(mpRcvState->mpBuffer, pchData, mpRcvState->mpBuffer->Length); + String^ cchDataStr = gcnew String(pchData); + + if (mpRcvState->mpAscCallback != nullptr) + mpRcvState->mpAscCallback->Invoke(cchDataStr); + } + + /* Empty the buffer */ + mpRcvState->mpBuffer = gcnew array(0); + } + } + + SOCKETRCVSTATE^ mpNewRcvState = gcnew SOCKETRCVSTATE; + mpNewRcvState->mpBuffer = gcnew array(256); + mpNewRcvState->mpAscCallback = mpRcvState->mpAscCallback; + mpNewRcvState->mpBinCallback = mpRcvState->mpBinCallback; + mpNewRcvState->mpClient = mpRcvState->mpClient; + mpNewRcvState->mpStream = mpRcvState->mpStream; + + try { + /* Start receving data again */ + mpRcvState->mpStream->BeginRead( + mpNewRcvState->mpBuffer, + 0, + mpNewRcvState->mpBuffer->Length, + gcnew AsyncCallback(&CTelnetClient::ReceiveProc), + mpNewRcvState + ); + } catch (SocketException^ e) { + delete e; + } +} + +/*********************************************************** + Sends string data to the remote host. + + @param cchData the string data to send +***********************************************************/ +void CTelnetClient::Send(String^ cchData) +{ + Text::Encoding^ mpEncoding = Text::ASCIIEncoding::Default; + array^ mpBytes = (array^)mpEncoding->GetBytes(cchData); + this->Send(mpBytes); +} + +/*********************************************************** + Sends byte data to the remote host. + + @param mpData the byte data to send +***********************************************************/ +void CTelnetClient::Send(array^ mpData) +{ + array^ mpNewData = gcnew array(0); + int n = 0; + + for (int i = 0; i < mpData->Length; i++) { + array::Resize( + mpNewData, + mpNewData->Length + (mpData[i] == 0xFF ? 2 : 1) + ); + mpNewData[n] = mpData[i]; + n++; + + /* Escape FF */ + if (mpData[i] == 0xFF) { + mpNewData[n] = mpData[i]; + n++; + } + } + + /* Detect end-of-record option and append IAC EOR if needed */ + if (m_mpServerOpts->ContainsKey(TELNET_OPTION_END_OF_RECORD)) { + array::Resize(mpNewData, mpNewData->Length + 2); + mpNewData[mpNewData->Length - 2] = TELNET_COMMAND_IAC; + mpNewData[mpNewData->Length - 1] = TELNET_COMMAND_END_OF_RECORD; + } + + this->Transmit(mpNewData); +} + +/*********************************************************** + Callback function for asynchronous sending. +***********************************************************/ +void CTelnetClient::SendProc(System::IAsyncResult^ mpResult) +{ + SOCKETSNDSTATE^ mpSndState = safe_cast(mpResult->AsyncState); + mpSndState->mpStream->EndWrite(mpResult); + mpSndState->mpClient->m_mpSendEvent->Set(); +} + +/*********************************************************** + Enables a terminal option for the server. + + @param nId the option id + @param fValue the option value (nullptr to unset) + + @return TRUE if the option is support, FALSE otherwise +***********************************************************/ +bool CTelnetClient::SetServerOption(int nId, bool fValue) +{ + return this->SetServerOption(nId, BitConverter::GetBytes(fValue)); +} + +/*********************************************************** + Enables a terminal option for the server. + + @param nId the option id + @param mpValue the option value (nullptr to unset) + + @return TRUE if the option is support, FALSE otherwise +***********************************************************/ +bool CTelnetClient::SetServerOption(int nId, array^ mpValue) +{ + /* Validate the input, and handle special cases */ + if (mpValue == nullptr && m_mpServerOpts->ContainsKey(nId)) + m_mpServerOpts->Remove(nId); + else if (mpValue == nullptr && !m_mpServerOpts->ContainsKey(nId)) + return false; + else if (mpValue != nullptr && m_mpServerOpts->ContainsKey(nId)) + return true; + + /* Select supported options */ + switch (nId) { + + /* Binary Transmission */ + case (TELNET_OPTION_BINARY): + return m_mpClientOpts->ContainsKey(safe_cast(TELNET_OPTION_TERM_TYPE)); + + /* Suppress Go Ahead */ + case (TELNET_OPTION_SUPPRESS_GA): + return m_mpClientOpts->ContainsKey(safe_cast(TELNET_OPTION_SUPPRESS_GA)); + + /* Terminal Type */ + case (TELNET_OPTION_TERM_TYPE): + return m_mpClientOpts->ContainsKey(TELNET_OPTION_TERM_TYPE); + + /* End of Record */ + case (TELNET_OPTION_END_OF_RECORD): + m_mpServerOpts->Add(TELNET_OPTION_END_OF_RECORD, true); + return true; + } + + /* Everything else is unsupported */ + return false; +} + +/*********************************************************** + Tests for both the existance of, and the value of a + server option. + + @param nId the option id + + @return a boolean representation of the value +***********************************************************/ +bool CTelnetClient::TestServerOption(int nId) +{ + /* Detect if the option isn't set */ + if (!m_mpServerOpts->ContainsKey(nId)) + return false; + + try { + return (bool)m_mpServerOpts[nId]; + } catch (InvalidCastException^ e) { + delete e; + } + + return false; +} + +/*********************************************************** + Sends byte data to the remote host. + + @param mpData the byte data to send +***********************************************************/ +void CTelnetClient::Transmit(array^ mpData) +{ + /* Copy our buffer to the queue */ + Monitor::Enter(this); + int nQueueIndex = m_mpTransmQueue->Length; + array::Resize( + m_mpTransmQueue, + m_mpTransmQueue->Length + mpData->Length + ); + array::Copy( + mpData, + 0, + m_mpTransmQueue, + nQueueIndex, + mpData->Length + ); + Monitor::Exit(this); + + if (this->FullDuplex || (!this->FullDuplex && m_fGoAhead)) + this->FlushTransmitQueue(); +} diff --git a/CTelnetClient.h b/CTelnetClient.h new file mode 100644 index 0000000..8a57ae6 --- /dev/null +++ b/CTelnetClient.h @@ -0,0 +1,241 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#pragma once + +#include "TelnetProtocol.h" + +namespace WinTN3270 +{ + /*********************************************************** + Client for communicating with a remote TELNET server. + ***********************************************************/ + public ref class CTelnetClient + { + + public: /* Public Delegates */ + delegate bool OnCertPolicyError( + System::Security::Cryptography::X509Certificates::X509Certificate^ mpCertificate, + System::Security::Cryptography::X509Certificates::X509Chain^ mpChain, + System::Net::Security::SslPolicyErrors ePolicyErrors); + delegate void OnReceiveASCII(System::String^ cchData); + delegate void OnReceiveBinary(cli::array^ mpData); + delegate void OnSend(); + + private: /* Private Member Attributes */ + System::String^ m_cchRemoteHost; + bool m_fGoAhead; + OnCertPolicyError^ m_mpCertErrorCbk; + System::Collections::Hashtable^ m_mpClientOpts; + System::Threading::EventWaitHandle^ m_mpSendEvent; + cli::array^ m_mpRcvCommandBuffer; + cli::array^ m_mpRcvDataBuffer; + OnReceiveASCII^ m_mpReceiveAscCbk; + OnReceiveBinary^ m_mpReceiveBinCbk; + System::Net::IPEndPoint^ m_mpRemoteIP; + OnSend^ m_mpSendCbk; + System::Collections::Hashtable^ m_mpServerOpts; + System::Net::Sockets::Socket^ m_mpSocket; + System::IO::Stream^ m_mpSocketStream; + TNSUBOPTION^ m_mpSubOpt; + cli::array^ m_mpTransmQueue; + + public: /* Public Properties */ + property bool BinaryTransmission + { + bool get() + { + return m_mpClientOpts->ContainsKey(safe_cast(TELNET_OPTION_BINARY)); + } + + void set(bool fValue) + { + if (!fValue && + m_mpClientOpts->ContainsKey(safe_cast(TELNET_OPTION_BINARY))) + { + m_mpClientOpts->Remove(safe_cast(TELNET_OPTION_BINARY)); + } + + m_mpClientOpts[safe_cast(TELNET_OPTION_BINARY)] = true; + } + } + + property OnCertPolicyError^ CertificateErrorCallback + { + OnCertPolicyError^ get() + { + System::Threading::Monitor::Enter(this); + OnCertPolicyError^ mpCertErrorCbk = m_mpCertErrorCbk; + System::Threading::Monitor::Exit(this); + + return mpCertErrorCbk; + } + + void set(OnCertPolicyError^ mpValue) + { + System::Threading::Monitor::Enter(this); + m_mpCertErrorCbk = mpValue; + System::Threading::Monitor::Exit(this); + } + } + + property bool Connected + { + bool get() { return m_mpSocket->Connected; } + } + + property bool EndOfRecord + { + bool get() + { + return m_mpClientOpts->ContainsKey(TELNET_OPTION_END_OF_RECORD); + } + + void set(bool fValue) + { + if (!fValue && + m_mpClientOpts->ContainsKey(TELNET_OPTION_END_OF_RECORD)) + { + m_mpClientOpts->Remove(TELNET_OPTION_END_OF_RECORD); + return; + } + + m_mpClientOpts[TELNET_OPTION_END_OF_RECORD] = true; + } + } + + property bool FullDuplex + { + bool get() + { + return m_mpClientOpts->ContainsKey(TELNET_OPTION_SUPPRESS_GA); + } + + void set(bool fValue) + { + if (!fValue && + m_mpClientOpts->ContainsKey(TELNET_OPTION_SUPPRESS_GA)) + { + m_mpClientOpts->Remove(TELNET_OPTION_SUPPRESS_GA); + return; + } + + m_mpClientOpts[TELNET_OPTION_SUPPRESS_GA] = true; + } + } + + property OnReceiveASCII^ ReceiveCallbackASCII + { + OnReceiveASCII^ get() { return m_mpReceiveAscCbk; } + + void set(OnReceiveASCII^ mpValue) + { + m_mpReceiveAscCbk = mpValue; + } + } + + property OnReceiveBinary^ ReceiveCallbackBinary + { + OnReceiveBinary^ get() { return m_mpReceiveBinCbk; } + + void set(OnReceiveBinary^ mpValue) + { + m_mpReceiveBinCbk = mpValue; + } + } + + property OnSend^ SendCallback + { + OnSend^ get() { return m_mpSendCbk; } + + void set(OnSend^ mpValue) + { + m_mpSendCbk = mpValue; + } + } + + property cli::array^ TerminalType + { + cli::array^ get() + { + if (!m_mpClientOpts->ContainsKey(TELNET_OPTION_TERM_TYPE)) + return nullptr; + + return (cli::array^)m_mpClientOpts[TELNET_OPTION_TERM_TYPE]; + } + + void set(cli::array^ mpValue) + { + if (mpValue == nullptr && + m_mpClientOpts->ContainsKey(TELNET_OPTION_TERM_TYPE)) + { + m_mpClientOpts->Remove(TELNET_OPTION_TERM_TYPE); + return; + } + + m_mpClientOpts[TELNET_OPTION_TERM_TYPE] = mpValue; + } + } + + private: /* Private Member Functions */ + bool CertValidationCallback( System::Object^ mpSender, + System::Security::Cryptography::X509Certificates::X509Certificate^ mpCertificate, + System::Security::Cryptography::X509Certificates::X509Chain^ mpChain, + System::Net::Security::SslPolicyErrors ePolicyErrors); + void FlushTransmitQueue(); + cli::array^ GetClientOption(int nId); + cli::array^ InterpretCommands(cli::array^ mpData); + static void ReceiveProc(System::IAsyncResult^ mpResult); + static void SendProc(System::IAsyncResult^ mpResult); + bool SetServerOption(int nId, bool fValue); + bool SetServerOption(int nId, cli::array^ mpValue); + void Transmit(cli::array^ mpData); + + public: /* Public Member Functions */ + CTelnetClient(System::String^ cchIPAddr, int nPort); + ~CTelnetClient(); + void Connect(); + void Connect(bool fSecure); + void Disconnect(); + cli::array^ GetServerOption(int nId); + void Send(System::String^ cchData); + void Send(cli::array^ mpData); + bool TestServerOption(int nId); + }; + + /* Socket Receive State */ + ref struct SOCKETRCVSTATE + { + cli::array^ mpBuffer; + CTelnetClient::OnReceiveASCII^ mpAscCallback; + CTelnetClient::OnReceiveBinary^ mpBinCallback; + CTelnetClient^ mpClient; + System::IO::Stream^ mpStream; + }; + + /* Socket Send State */ + ref struct SOCKETSNDSTATE + { + CTelnetClient::OnSend^ mpCallback; + CTelnetClient^ mpClient; + System::IO::Stream^ mpStream; + }; +} diff --git a/CWndMain.cpp b/CWndMain.cpp new file mode 100644 index 0000000..f8d2c47 --- /dev/null +++ b/CWndMain.cpp @@ -0,0 +1,119 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#include "stdafx.h" +#include "CWndMain.h" + +using namespace WinTN3270; +using namespace System; +using namespace System::Net::Security; +using namespace System::Security::Cryptography::X509Certificates; +using namespace System::Windows; + +/*********************************************************** + Handles the form load event. +***********************************************************/ +Void CWndMain::CWndMain_Load(Object^ sender, EventArgs^ e) +{ + /* Prep the canvas */ + pbxCanvas->BackColor = Color::Black; + + m_mpClient = gcnew CClient3270("mvs.example.com", 992, 2); + m_mpClient->Canvas = pbxCanvas; + m_mpClient->CertificateErrorCallback = gcnew CClient3270::OnCertPolicyError( + this, + &CWndMain::m_mpClient_CertPolicyError + ); + m_mpClient->Connect(true); +} + +/*********************************************************** + Handles the key down event for the canvas. +***********************************************************/ +System::Void CWndMain::CWndMain_KeyDown(System::Object^ sender, KeyEventArgs^ e) +{ + switch (e->KeyCode) { + + case ConsoleKey::LeftArrow: + m_mpClient->MoveCursor(-1, 0, true); + break; + + case ConsoleKey::RightArrow: + m_mpClient->MoveCursor(1, 0, true); + break; + + case ConsoleKey::UpArrow: + m_mpClient->MoveCursor(0, -1, true); + break; + + case ConsoleKey::DownArrow: + m_mpClient->MoveCursor(0, 1, true); + break; + + case ConsoleKey::F12: + m_mpClient->KeyPress(gcnew KeyPressEventArgs((wchar_t) ConsoleKey::F12)); + break; + } +} + +/*********************************************************** + Handles the key press event for the canvas. +***********************************************************/ +System::Void CWndMain::CWndMain_KeyPress(System::Object^ sender, KeyPressEventArgs^ e) +{ + m_mpClient->KeyPress(e, this->ModifierKeys); +} + +/*********************************************************** + Handles the form paint event. +***********************************************************/ +void CWndMain::OnPaint(PaintEventArgs^ e) +{ + /* Re-draw the terminal display */ + m_mpClient->RepaintScreen(); +} + +/*********************************************************** + Callback for handling SSL policy errors. + + @param mpCertificate the cert to validate + @param mpChain the certificate chain + @param ePolicyErrors any policy errors + + @return TRUE for valid, FALSE otherwise +***********************************************************/ +bool CWndMain::m_mpClient_CertPolicyError(X509Certificate^ mpCertificate, + X509Chain^ mpChain, SslPolicyErrors ePolicyErrors) +{ + String^ mpErrors = ""; + + if ((ePolicyErrors & SslPolicyErrors::RemoteCertificateChainErrors) == SslPolicyErrors::RemoteCertificateChainErrors) + mpErrors += "\n\n* The security certificate is not from a trusted authority."; + if ((ePolicyErrors & SslPolicyErrors::RemoteCertificateNameMismatch) == SslPolicyErrors::RemoteCertificateNameMismatch) + mpErrors += "\n\n* The name on the security certificate is invalid or does not match the name of the remote host."; + if ((ePolicyErrors & SslPolicyErrors::RemoteCertificateNotAvailable) == SslPolicyErrors::RemoteCertificateNotAvailable) + mpErrors += "\n\n* The security certificate is not available."; + + return MessageBox::Show("There is a problem with remote host's security certificate!" + + mpErrors + "\n\nDo you want to proceed?", "Security Warning", + MessageBoxButtons::YesNo, MessageBoxIcon::Exclamation, + MessageBoxDefaultButton::Button2) == Forms::DialogResult::Yes; +} diff --git a/CWndMain.h b/CWndMain.h new file mode 100644 index 0000000..5e8589c --- /dev/null +++ b/CWndMain.h @@ -0,0 +1,152 @@ +#pragma once + +#include "CClient3270.h" + +namespace WinTN3270 { + + using namespace System; + using namespace System::ComponentModel; + using namespace System::Collections; + using namespace System::Windows::Forms; + using namespace System::Data; + using namespace System::Drawing; + + /// + /// Summary for Form1 + /// + /// WARNING: If you change the name of this class, you will need to change the + /// 'Resource File Name' property for the managed resource compiler tool + /// associated with all .resx files this class depends on. Otherwise, + /// the designers will not be able to interact properly with localized + /// resources associated with this form. + /// + public ref class CWndMain : public System::Windows::Forms::Form + { + public: + CWndMain(void) + { + InitializeComponent(); + // + //TODO: Add the constructor code here + // + } + + protected: + /// + /// Clean up any resources being used. + /// + ~CWndMain() + { + if (components) + { + delete components; + } + } + private: System::Windows::Forms::MenuStrip^ mnuMain; + private: System::Windows::Forms::StatusStrip^ sbrMain; + protected: + + protected: + + private: System::Windows::Forms::ToolStrip^ tlbMain; + private: System::Windows::Forms::PictureBox^ pbxCanvas; + + + + + private: + /// + /// Required designer variable. + /// + System::ComponentModel::Container ^components; + +#pragma region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + void InitializeComponent(void) + { + this->mnuMain = (gcnew System::Windows::Forms::MenuStrip()); + this->sbrMain = (gcnew System::Windows::Forms::StatusStrip()); + this->tlbMain = (gcnew System::Windows::Forms::ToolStrip()); + this->pbxCanvas = (gcnew System::Windows::Forms::PictureBox()); + (cli::safe_cast(this->pbxCanvas))->BeginInit(); + this->SuspendLayout(); + // + // mnuMain + // + this->mnuMain->Location = System::Drawing::Point(0, 0); + this->mnuMain->Name = L"mnuMain"; + this->mnuMain->Size = System::Drawing::Size(817, 24); + this->mnuMain->TabIndex = 0; + this->mnuMain->Text = L"menuStrip1"; + // + // sbrMain + // + this->sbrMain->Location = System::Drawing::Point(0, 539); + this->sbrMain->Name = L"sbrMain"; + this->sbrMain->Size = System::Drawing::Size(817, 22); + this->sbrMain->TabIndex = 1; + this->sbrMain->Text = L"statusStrip1"; + // + // tlbMain + // + this->tlbMain->Location = System::Drawing::Point(0, 24); + this->tlbMain->Name = L"tlbMain"; + this->tlbMain->Size = System::Drawing::Size(817, 25); + this->tlbMain->TabIndex = 2; + this->tlbMain->Text = L"toolStrip1"; + // + // pbxCanvas + // + this->pbxCanvas->Anchor = static_cast((((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Bottom) + | System::Windows::Forms::AnchorStyles::Left) + | System::Windows::Forms::AnchorStyles::Right)); + this->pbxCanvas->Location = System::Drawing::Point(0, 51); + this->pbxCanvas->Name = L"pbxCanvas"; + this->pbxCanvas->Size = System::Drawing::Size(817, 485); + this->pbxCanvas->TabIndex = 3; + this->pbxCanvas->TabStop = false; + // + // CWndMain + // + this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); + this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; + this->ClientSize = System::Drawing::Size(817, 561); + this->Controls->Add(this->pbxCanvas); + this->Controls->Add(this->tlbMain); + this->Controls->Add(this->sbrMain); + this->Controls->Add(this->mnuMain); + this->MainMenuStrip = this->mnuMain; + this->Name = L"CWndMain"; + this->Text = L"CWndMain"; + this->KeyPress += gcnew System::Windows::Forms::KeyPressEventHandler(this, &CWndMain::CWndMain_KeyPress); + this->KeyDown += gcnew System::Windows::Forms::KeyEventHandler(this, &CWndMain::CWndMain_KeyDown); + this->Load += gcnew System::EventHandler(this, &CWndMain::CWndMain_Load); + (cli::safe_cast(this->pbxCanvas))->EndInit(); + this->ResumeLayout(false); + this->PerformLayout(); + + } +#pragma endregion + + private: /* Private Member Attributes */ + CClient3270^ m_mpClient; + + private: /* Private Member Functions */ + System::Void CWndMain_Load(System::Object^ sender, System::EventArgs^ e); + System::Void CWndMain_KeyDown(System::Object^ sender, + System::Windows::Forms::KeyEventArgs^ e); + System::Void CWndMain_KeyPress(System::Object^ sender, + System::Windows::Forms::KeyPressEventArgs^ e); + bool m_mpClient_CertPolicyError( + System::Security::Cryptography::X509Certificates::X509Certificate^ mpCertificate, + System::Security::Cryptography::X509Certificates::X509Chain^ mpChain, + System::Net::Security::SslPolicyErrors ePolicyErrors); + + protected: /* Overrides */ + virtual void OnPaint(PaintEventArgs^ e) override; +}; +} + diff --git a/CWndMain.resx b/CWndMain.resx new file mode 100644 index 0000000..7adabff --- /dev/null +++ b/CWndMain.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 126, 17 + + + 236, 17 + + \ No newline at end of file diff --git a/IBM3270.h b/IBM3270.h new file mode 100644 index 0000000..3794891 --- /dev/null +++ b/IBM3270.h @@ -0,0 +1,120 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +/*********************************************************** + Constants for the IBM 3270 data stream protocol. +***********************************************************/ + +#pragma once + +/* Commands (ASCII) */ +#define IBM3270DS_CMD_EAU 0x0F /* Erase All Unprotected */ +#define IBM3270DS_CMD_EW 0x05 /* Erase Write */ +#define IBM3270DS_CMD_EWA 0x0D /* Erase Write Alternate */ +#define IBM3270DS_CMD_NOP 0x03 /* No Operation */ +#define IBM3270DS_CMD_RB 0x02 /* Read Buffer */ +#define IBM3270DS_CMD_RM 0x06 /* Read Modified */ +#define IBM3270DS_CMD_RMA 0x0E /* Read Modified All */ +#define IBM3270DS_CMD_W 0x01 /* Write */ +#define IBM3270DS_CMD_WSF 0x11 /* Write Structured Field */ + +/* Commands (EBCDIC) */ +#define IBM3270DS_CMD_EAU_EBCDIC 0x6F /* Erase All Unprotected */ +#define IBM3270DS_CMD_EW_EBCDIC 0xF5 /* Erase Write */ +#define IBM3270DS_CMD_EWA_EBCDIC 0x7E /* Erase Write Alternate */ +#define IBM3270DS_CMD_RB_EBCDIC 0xF2 /* Read Buffer */ +#define IBM3270DS_CMD_RM_EBCDIC 0xF6 /* Read Modified */ +#define IBM3270DS_CMD_RMA_EBCDIC 0x6E /* Read Modified All */ +#define IBM3270DS_CMD_W_EBCDIC 0xF1 /* Write */ +#define IBM3270DS_CMD_WSF_EBCDIC 0xF3 /* Write Structured Field */ + +/* Orders */ +#define IBM3270DS_ORDER_EUA 0x12 /* Erase Unprotected to Address */ +#define IBM3270DS_ORDER_GE 0x08 /* Graphic Escape */ +#define IBM3270DS_ORDER_IC 0x13 /* Insert Cursor */ +#define IBM3270DS_ORDER_MF 0x2C /* Modify Field */ +#define IBM3270DS_ORDER_PT 0x05 /* Program Tab */ +#define IBM3270DS_ORDER_RA 0x3C /* Repeat to Address */ +#define IBM3270DS_ORDER_SA 0x28 /* Set Attribute */ +#define IBM3270DS_ORDER_SBA 0x11 /* Set Buffer Address */ +#define IBM3270DS_ORDER_SF 0x1D /* Start Field */ +#define IBM3270DS_ORDER_SFE 0x29 /* Start Field Extended */ + +/* Action IDs */ +#define IBM3270DS_AID_CLEAR 0x6D +#define IBM3270DS_AID_ENTER 0x7D +#define IBM3270DS_AID_NONE 0x60 +#define IBM3270DS_AID_PA1 0x6C +#define IBM3270DS_AID_PA2 0x6E +#define IBM3270DS_AID_PA3 0x6B +#define IBM3270DS_AID_PF1 0xF1 +#define IBM3270DS_AID_PF2 0xF2 +#define IBM3270DS_AID_PF3 0xF3 +#define IBM3270DS_AID_PF4 0xF4 +#define IBM3270DS_AID_PF5 0xF5 +#define IBM3270DS_AID_PF6 0xF6 +#define IBM3270DS_AID_PF7 0xF7 +#define IBM3270DS_AID_PF8 0xF8 +#define IBM3270DS_AID_PF9 0xF9 +#define IBM3270DS_AID_PF10 0x7A +#define IBM3270DS_AID_PF11 0x7B +#define IBM3270DS_AID_PF12 0x7C +#define IBM3270DS_AID_PF13 0xC1 +#define IBM3270DS_AID_PF14 0xC2 +#define IBM3270DS_AID_PF15 0xC3 +#define IBM3270DS_AID_PF16 0xC4 +#define IBM3270DS_AID_PF17 0xC5 +#define IBM3270DS_AID_PF18 0xC6 +#define IBM3270DS_AID_PF19 0xC7 +#define IBM3270DS_AID_PF20 0xC8 +#define IBM3270DS_AID_PF21 0xC9 +#define IBM3270DS_AID_PF22 0x4A +#define IBM3270DS_AID_PF23 0x4B +#define IBM3270DS_AID_PF24 0x4C +#define IBM3270DS_AID_SF 0x88 + +/* Structured Field Operations */ +#define IBM3270DS_SF_ERASE_RESET 0x03 /* Erase/Reset */ +#define IBM3270DS_SF_OUTBOUND_DS 0x40 /* Outbound 3270 DS */ +#define IBM3270DS_SF_QUERY_REPLY 0x81 /* Query Reply */ +#define IBM3270DS_SF_READ_PART 0x01 /* Read Partition */ +#define IBM3270DS_SF_RESET_PART 0x00 /* Reset Partition */ +#define IBM3270DS_SF_SET_REPLY_MODE 0x09 /* Set Reply Mode */ +#define IBM3270DS_SF_TRANSFER_DATA 0xd0 /* File Transfer Open Request */ + +/* SF Erase/Write Operations */ +#define IBM3270DS_SF_ER_DEFAULT 0x00 /* Default */ +#define IBM3270DS_SF_ER_ALT 0x80 /* Alternate */ + +/* SF Read Partition Operations */ +#define IBM3270DS_SF_RP_QUERY 0x02 /* Query */ +#define IBM3270DS_SF_RP_QLIST 0x03 /* Query List */ +#define IBM3270DS_SF_RP_RB 0xF2 /* Read Buffer */ +#define IBM3270DS_SF_RP_RM 0xF6 /* Read Modified */ +#define IBM3270DS_SF_RP_RMA 0x6E /* Read Modified All */ + +/* SF Set Reply Mode Operations */ +#define IBM3270DS_SF_SRM_FIELD 0x00 /* Field */ +#define IBM3270DS_SF_SRM_XFIELD 0x01 /* Extended Field */ +#define IBM3270DS_SF_SRM_CHAR 0x02 /* Character */ + +/* SF Query Reply Codes */ +#define IBM3270DS_SF_QR_NULL 0xFF /* Null */ diff --git a/TelnetProtocol.h b/TelnetProtocol.h new file mode 100644 index 0000000..d71626a --- /dev/null +++ b/TelnetProtocol.h @@ -0,0 +1,61 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +/*********************************************************** + Constants and data structures for TELNET. +***********************************************************/ + +#pragma once + +/* Commands */ +#define TELNET_COMMAND_END_OF_RECORD 0xEF +#define TELNET_COMMAND_SE 0xF0 +#define TELNET_COMMAND_NOP 0xF1 +#define TELNET_COMMAND_DATA_MARK 0xF2 +#define TELNET_COMMAND_BREAK 0xF3 +#define TELNET_COMMAND_INTERRUPT 0xF4 +#define TELNET_COMMAND_ABORT 0xF5 +#define TELNET_COMMAND_ARE_YOU_THERE 0xF6 +#define TELNET_COMMAND_ERASE_CHAR 0xF7 +#define TELNET_COMMAND_ERASE_LINE 0xF8 +#define TELNET_COMMAND_GA 0xF9 +#define TELNET_COMMAND_SB 0xFA +#define TELNET_COMMAND_WILL 0xFB +#define TELNET_COMMAND_WONT 0xFC +#define TELNET_COMMAND_DO 0xFD +#define TELNET_COMMAND_DONT 0xFE +#define TELNET_COMMAND_IAC 0xFF + +/* Options */ +#define TELNET_OPTION_BINARY 0x00 +#define TELNET_OPTION_ECHO 0x01 +#define TELNET_OPTION_SUPPRESS_GA 0x03 +#define TELNET_OPTION_TERM_TYPE 0x18 +#define TELNET_OPTION_END_OF_RECORD 0x19 +#define TELNET_OPTION_TN3270E 0x28 + +/* Sub-Option Structure */ +ref struct TNSUBOPTION +{ + cli::array^ pchValue; + bool fValueRequired; + int nOptionId; +}; diff --git a/WinTN3270.cpp b/WinTN3270.cpp new file mode 100644 index 0000000..56936d4 --- /dev/null +++ b/WinTN3270.cpp @@ -0,0 +1,43 @@ +/*********************************************************** + WinTN3270 + Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu) + + This software is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; + either version 2, or (at your option) any later version. + + This software is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public + License along with this software; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +***********************************************************/ + +#include "stdafx.h" +#include "CWndMain.h" + +using namespace WinTN3270; + +/*********************************************************** + Application entry-point function + + @param args the command line arguments + + @return the program return code +***********************************************************/ +[STAThreadAttribute] +int main(array ^args) +{ + /* Enabling Windows XP visual effects before any controls are created */ + Application::EnableVisualStyles(); + Application::SetCompatibleTextRenderingDefault(false); + + Application::Run(gcnew CWndMain()); + return 0; +} diff --git a/WinTN3270.sln b/WinTN3270.sln new file mode 100644 index 0000000..91fbd71 --- /dev/null +++ b/WinTN3270.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinTN3270", "WinTN3270.vcxproj", "{7E3C3C84-0CB0-4D5E-A97D-DFCFE401AFE4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7E3C3C84-0CB0-4D5E-A97D-DFCFE401AFE4}.Debug|Win32.ActiveCfg = Debug|Win32 + {7E3C3C84-0CB0-4D5E-A97D-DFCFE401AFE4}.Debug|Win32.Build.0 = Debug|Win32 + {7E3C3C84-0CB0-4D5E-A97D-DFCFE401AFE4}.Release|Win32.ActiveCfg = Release|Win32 + {7E3C3C84-0CB0-4D5E-A97D-DFCFE401AFE4}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/WinTN3270.vcproj b/WinTN3270.vcproj new file mode 100644 index 0000000..6bbaff2 --- /dev/null +++ b/WinTN3270.vcproj @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WinTN3270.vcxproj b/WinTN3270.vcxproj new file mode 100644 index 0000000..801c0cd --- /dev/null +++ b/WinTN3270.vcxproj @@ -0,0 +1,154 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {7E3C3C84-0CB0-4D5E-A97D-DFCFE401AFE4} + WinTN3270 + ManagedCProj + + + + Application + Unicode + Pure + true + + + Application + Unicode + Pure + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Use + Level3 + ProgramDatabase + + + + + true + true + Windows + main + MachineX86 + + + + + WIN32;NDEBUG;%(PreprocessorDefinitions) + MultiThreadedDLL + Use + Level3 + ProgramDatabase + + + + + true + Windows + main + MachineX86 + + + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + + + + + + + + + Create + Create + + + + + + + + + + CppForm + + + + + + + + + + + + + + + CWndMain.h + Designer + + + + + + \ No newline at end of file diff --git a/app.ico b/app.ico new file mode 100644 index 0000000000000000000000000000000000000000..3a5525fd794f7a7c5c8e6187f470ea3af38cd2b6 GIT binary patch literal 1078 zcmeHHJr05}7=1t!Hp3A*8IHkVf+j?-!eHY14Gtcw1Eb*_9>Bq^zETJ@GKj{_2j4$w zo9}xCh!8{T3=X##Skq>ikMjsvB|y%crWBM2iW(4pI}c%z6%lW!=~4v77#3{z!dmB1 z__&l)-{KUYR+|8|;wB^R|9ET$J@(@=#rd^=)qs85?vAy(PSF5CyNkus435LVkZ$rj zNw|JG-P7^hF<(;#o*Vk}5R#e|^13tBbQkeF?djULtvqyxd3<{9 literal 0 HcmV?d00001 diff --git a/app.rc b/app.rc new file mode 100644 index 0000000..8c1d640 --- /dev/null +++ b/app.rc @@ -0,0 +1,63 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon placed first or with lowest ID value becomes application icon + +LANGUAGE 9, 1 +#pragma code_page(1252) +1 ICON "app.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" + "\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..1f2251c --- /dev/null +++ b/resource.h @@ -0,0 +1,3 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by app.rc diff --git a/stdafx.cpp b/stdafx.cpp new file mode 100644 index 0000000..44452ed --- /dev/null +++ b/stdafx.cpp @@ -0,0 +1,7 @@ +// stdafx.cpp : source file that includes just the standard includes +// WinTN3270.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 0000000..8a24125 --- /dev/null +++ b/stdafx.h @@ -0,0 +1,6 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +#pragma once + +#include