commit 588e118469f32a175dec3699c00ea3eca8fb34e7 Author: Bob Carroll Date: Thu Oct 17 15:07:31 2013 -0700 import from subversion 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 0000000..3a5525f Binary files /dev/null and b/app.ico differ 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