2025-04-27 07:49:33 -04:00

624 lines
16 KiB
C++

#include <wbemidl.h>
#include <wbemcomn.h>
#include <wbemutil.h>
#include <cominit.h>
#include "Pager.h"
#define LOGFILE_PROPNAME_NUMBER L"PhoneNumber"
#define LOGFILE_PROPNAME_MESSAGE L"Message"
#define LOGFILE_PROPNAME_ID L"ID"
#define LOGFILE_PROPNAME_PORT L"Port"
#define LOGFILE_PROPNAME_BAUDRATE L"BaudRate"
#define LOGFILE_PROPNAME_SETUPSTR L"ModemSetupString"
#define LOGFILE_PROPNAME_TIMEOUT L"AnswerTimeout"
#define ACK '\x06'
#define NAK '\x15'
#define EOT '\x04'
#define NUL '\x00'
HRESULT STDMETHODCALLTYPE CPagerConsumer::XProvider::FindConsumer(
IWbemClassObject* pLogicalConsumer,
IWbemUnboundObjectSink** ppConsumer)
{
HRESULT hr = WBEM_E_FAILED;
DEBUGTRACE((LOG_ESS, "Pager: Find Consumer\n"));
CPagerSink* pSink = new CPagerSink(m_pObject->m_pControl);
if (pSink)
{
if (SUCCEEDED(hr = pSink->SetConsumer(pLogicalConsumer)))
hr = pSink->QueryInterface(IID_IWbemUnboundObjectSink, (void**)ppConsumer);
else
pSink->Release();
}
else
hr = WBEM_E_OUT_OF_MEMORY;
DEBUGTRACE((LOG_ESS, "Pager: Find Consumer returning 0x%08X\n", hr));
return hr;
}
HRESULT STDMETHODCALLTYPE CPagerConsumer::XInit::Initialize(
LPWSTR, LONG, LPWSTR, LPWSTR, IWbemServices*, IWbemContext*,
IWbemProviderInitSink* pSink)
{
pSink->SetStatus(0, 0);
return 0;
}
void* CPagerConsumer::GetInterface(REFIID riid)
{
if(riid == IID_IWbemEventConsumerProvider)
return &m_XProvider;
else if(riid == IID_IWbemProviderInit)
return &m_XInit;
else return NULL;
}
CPagerSink::~CPagerSink()
{
}
HRESULT CPagerSink::SetConsumer(IWbemClassObject* pLogicalConsumer)
{
// Get the information
// ===================
HRESULT hres;
VARIANT v;
VariantInit(&v);
hres = pLogicalConsumer->Get(LOGFILE_PROPNAME_NUMBER, 0, &v, NULL, NULL);
if(FAILED(hres) || V_VT(&v) != VT_BSTR)
return WBEM_E_INVALID_PARAMETER;
m_phoneNumber = V_BSTR(&v);
VariantClear(&v);
hres = pLogicalConsumer->Get(LOGFILE_PROPNAME_MESSAGE, 0, &v, NULL, NULL);
if(FAILED(hres) || V_VT(&v) != VT_BSTR)
return WBEM_E_INVALID_PARAMETER;
m_messageTemplate.SetTemplate(V_BSTR(&v));
VariantClear(&v);
hres = pLogicalConsumer->Get(LOGFILE_PROPNAME_ID, 0, &v, NULL, NULL);
if(FAILED(hres) || V_VT(&v) != VT_BSTR)
return WBEM_E_INVALID_PARAMETER;
m_pagerID = V_BSTR(&v);
VariantClear(&v);
hres = pLogicalConsumer->Get(LOGFILE_PROPNAME_PORT, 0, &v, NULL, NULL);
if(FAILED(hres) || V_VT(&v) != VT_BSTR)
return WBEM_E_INVALID_PARAMETER;
m_port = V_BSTR(&v);
VariantClear(&v);
hres = pLogicalConsumer->Get(LOGFILE_PROPNAME_BAUDRATE, 0, &v, NULL, NULL);
if (SUCCEEDED(hres))
{
if (V_VT(&v) == VT_I4)
m_baudRate = V_I4(&v);
else if (V_VT(&v) == VT_NULL)
m_baudRate = INVALID_BAUD_RATE;
else
return WBEM_E_INVALID_PARAMETER;
}
else
return hres;
VariantClear(&v);
hres = pLogicalConsumer->Get(LOGFILE_PROPNAME_TIMEOUT, 0, &v, NULL, NULL);
if (SUCCEEDED(hres))
{
if (V_VT(&v) == VT_I4)
m_timeout = 1000 * V_I4(&v);
else if (V_VT(&v) == VT_NULL)
m_timeout = 30000;
else
return WBEM_E_INVALID_PARAMETER;
}
else
return hres;
VariantClear(&v);
hres = pLogicalConsumer->Get(LOGFILE_PROPNAME_SETUPSTR, 0, &v, NULL, NULL);
if(FAILED(hres) || !((V_VT(&v) == VT_BSTR) || (V_VT(&v) == VT_NULL)))
return WBEM_E_INVALID_PARAMETER;
m_modemSetupString = V_BSTR(&v);
m_modemSetupString += L"\r";
VariantClear(&v);
return hres;
}
HRESULT STDMETHODCALLTYPE CPagerSink::XSink::IndicateToConsumer(
IWbemClassObject* pLogicalConsumer, long lNumObjects,
IWbemClassObject** apObjects)
{
HRESULT hr = WBEM_S_NO_ERROR;
DEBUGTRACE((LOG_ESS, "Pager: CPagerSink::IndicateToConsumer %d objects\n", lNumObjects));
for (int i = 0; i < lNumObjects; i++)
{
// Apply the template to the event
// ===============================
BSTR strText = m_pObject->m_messageTemplate.Apply(apObjects[i]);
// call somebody & let 'em know about it....
if(strText)
{
hr = m_pObject->RingMeUp(strText);
SysFreeString(strText);
}
else
hr = WBEM_E_FAILED;
}
return hr;
}
// listens to the modem for a while,
// searches for the string 'OK' in the string read
// modem should echo back OK if everything's hunky dory
HRESULT CPagerSink::IsOK(HANDLE hPort)
{
HRESULT hr = WBEM_E_FAILED;
SetTimeout(hPort, 1000);
// monitor the return from the modem
BYTE buffer[101];
ZeroMemory(buffer, 101);
DWORD dwErr = 0;
DWORD bytesWryt;
int nTries = 0;
while (FAILED(hr) &&
ReadFile(hPort, &buffer, 100, &bytesWryt, NULL) &&
(nTries++ < 3))
if (strstr(_strupr((char*)buffer), "OK"))
hr = WBEM_S_NO_ERROR;
if (SUCCEEDED(hr))
DEBUGTRACE((LOG_ESS, "Pager: modem responds OK\n"));
else
ERRORTRACE((LOG_ESS, "Pager: modem does not respond OK (%s)\n", buffer));
return hr;
}
// listens to the modem for a while,
// searches for the ACK char
bool CPagerSink::GotACK(HANDLE hPort)
{
return (GetResponse(hPort) == ACK);
}
// listens to the modem for a while,
// searches for the NAK char
bool CPagerSink::GotNAK(HANDLE hPort)
{
return (GetResponse(hPort) == NAK);
}
// listens to the modem for a while,
// searches for the string 'CONNECT' in the string read
// caller should set timeout to a fairly large number before calling this
HRESULT CPagerSink::IsConnected(HANDLE hPort)
{
HRESULT hr = WBEM_E_FAILED;
// monitor the return from the modem
BYTE buffer[101];
ZeroMemory(buffer, 101);
DWORD dwErr = 0;
DWORD bytesWryt;
if (ReadFile(hPort, &buffer, 100, &bytesWryt, NULL))
{
if (strstr(_strupr((char*)buffer), "CONNECT"))
{
DEBUGTRACE((LOG_ESS, "Pager: connected (%s)\n", buffer));
hr = WBEM_S_NO_ERROR;
}
else
ERRORTRACE((LOG_ESS, "Pager: Failed to connect\"%s\"", buffer));
}
return hr;
}
// listens to the modem for a while,
// searches for the string that signifies we're ready to go
bool CPagerSink::ReadyToProceed(HANDLE hPort)
{
bool bRet = false;
SetTimeout(hPort, 10000);
// monitor the return from the modem
BYTE buffer[101];
ZeroMemory(buffer, 101);
DWORD dwErr = 0;
DWORD bytesWryt;
int nTries = 0;
while (!bRet && (nTries++ < 3) && ReadFile(hPort, &buffer, 100, &bytesWryt, NULL))
if (strstr((char*)buffer, "[p"))
bRet = true;
else
ZeroMemory(buffer, 101);
if (bRet)
DEBUGTRACE((LOG_ESS, "Pager: Message go-ahead\n"));
else
ERRORTRACE((LOG_ESS, "Pager: FAILED to receive go-ahead\"%s\"\n", buffer));
return bRet;
}
// if timeout == 0 (the default) it will
// set a (hopefully) reasonable timeout
// for waiting for a response from the modem itself
// timeout is in milliseconds
void CPagerSink::SetTimeout(HANDLE hPort, DWORD timeout)
{
COMMTIMEOUTS commTimeout;
if (timeout == 0)
{
commTimeout.WriteTotalTimeoutConstant = 100;
commTimeout.ReadTotalTimeoutConstant = 100;
}
else
{
commTimeout.WriteTotalTimeoutConstant = timeout;
commTimeout.ReadTotalTimeoutConstant = timeout;
}
commTimeout.ReadIntervalTimeout = 20;
commTimeout.ReadTotalTimeoutMultiplier = 10;
commTimeout.WriteTotalTimeoutMultiplier = 10;
SetCommTimeouts(hPort, &commTimeout);
}
// configure modem using both our 'well known settings'
// and the modem control string in the MOF
// upon successful return *ppDCB points to a DCB representing
// the orginal state of the modem before we whacked it.
// caller's responsibility to delete the ppDCB
HRESULT CPagerSink::ConfigureModem(HANDLE hPort, DCB** ppDCB)
{
DEBUGTRACE((LOG_ESS, "Pager: CPagerSink::ConfigureModem\n"));
HRESULT hr = WBEM_E_FAILED;
*ppDCB = new DCB;
if (!*ppDCB)
hr = WBEM_E_OUT_OF_MEMORY;
else
{
if (!GetCommState(hPort, *ppDCB))
hr = WBEM_E_FAILED;
else
{
SetTimeout(hPort);
// we're here - let's set some settings
// first copy the existing settings...
DCB dcb = **ppDCB;
// ...and then tweak the bits we want tweaked
if (m_baudRate != INVALID_BAUD_RATE)
dcb.BaudRate = m_baudRate;
// following setting is per the TAP standard
// E-7-1
dcb.Parity = 2; // even parity
dcb.ByteSize = 7; // seven bits
dcb.StopBits = 0; // per the spec, 0 == "one stop bit"
dcb.fParity = 1; // enable parity checking
if (SetCommState(hPort, &dcb))
{
hr = WBEM_S_NO_ERROR;
DWORD bytesWryt;
// Is there an echo in here?
// if so - turn it OFF!
const char* echoOff = "ATE0\r";
WriteFile(hPort, echoOff, strlen(echoOff), &bytesWryt, NULL);
// this will clear the buffer, but we won't write it down...
IsOK(hPort);
if (SUCCEEDED(hr) && (m_modemSetupString.Length() != 0))
{
LPSTR pSetup = m_modemSetupString.GetLPSTR();
if (!pSetup)
hr = WBEM_E_OUT_OF_MEMORY;
else
{
// setup string is problem of originator, we'll believe it:
WriteFile(hPort, pSetup, strlen(pSetup), &bytesWryt, NULL);
delete pSetup;
hr = IsOK(hPort);
}
}
}
else
ERRORTRACE((LOG_ESS, "Pager: SetCommState Failed: %d", GetLastError()));
}
}
return hr;
}
HRESULT CPagerSink::DialUp(HANDLE hPort)
{
DEBUGTRACE((LOG_ESS,"Pager: CPagerSink::DialUp\n"));
HRESULT hr = WBEM_E_FAILED;
LPSTR pNumber = m_phoneNumber.GetLPSTR();
if (!pNumber)
hr = WBEM_E_OUT_OF_MEMORY;
else
{
char buf[100];
DWORD bytesWryt;
wsprintf(buf, "ATDT%s\r", pNumber);
WriteFile(hPort, buf, strlen(buf), &bytesWryt, NULL);
SetTimeout(hPort, m_timeout);
hr = IsConnected(hPort);
delete pNumber;
SetTimeout(hPort);
}
return hr;
}
// SHOULD return ACK, NAK, EOT, or NUL
// will keep trying until it gets one or another;
char CPagerSink::GetResponse(HANDLE hPort)
{
SetTimeout(hPort, 5000);
// monitor the return from the modem
BYTE buffer[101];
ZeroMemory(buffer, 101);
DWORD dwErr = 0;
DWORD bytesWryt;
int nTries = 0;
// see if we see a response we might be interested in
while ((nTries++ < 3) && ReadFile(hPort, &buffer, 100, &bytesWryt, NULL))
{
if (strchr((const char *)buffer, ACK))
{
DEBUGTRACE((LOG_ESS, "Pager: service responds ACK\n"));
return ACK;
}
else if (strchr((const char *)buffer, NAK))
{
DEBUGTRACE((LOG_ESS, "Pager: service responds NAK\n"));
return NAK;
}
else if (strchr((const char *)buffer, EOT))
{
DEBUGTRACE((LOG_ESS, "Pager: service responds EOT\n"));
return EOT;
}
else
// go back, jack, and do it again...
ZeroMemory(buffer, 101);
}
// welp, tried three times & couldn't get it. bail.
return NUL;
};
// gonna sit here and listen for a prompt
// but we're gonna proceed even if we don't...
void CPagerSink::GetPrompt(HANDLE hPort)
{
int nAttempts = 0;
bool gotPrompt = false;
DWORD bytesWrytten;
// per TAP spec - they're supposed to respond within ONE second
SetTimeout(hPort, 1500);
char buf[101];
do
{
WriteFile(hPort, "\r", 1, &bytesWrytten, NULL);
ZeroMemory(buf, 101);
if (ReadFile(hPort, &buf, 100, &bytesWrytten, NULL))
if (strstr(_strupr((char*)buf), "ID="))
gotPrompt = true;
}
while ((++nAttempts < 3) && !gotPrompt);
if (gotPrompt)
DEBUGTRACE((LOG_ESS, "Pager: Received prompt\n"));
else
ERRORTRACE((LOG_ESS, "Pager: Did not receive prompt\n"));
}
HRESULT CPagerSink::Login(HANDLE hPort)
{
HRESULT hr = WBEM_E_FAILED;
GetPrompt(hPort);
// this *should* be universal. Maybe not, too...
char* loginString = "\x1BPG1\r";
DWORD bytesWrytten;
SetTimeout(hPort, 10000);
WriteFile(hPort, loginString, strlen(loginString), &bytesWrytten, NULL);
if (GotACK(hPort))
{
hr = WBEM_S_NO_ERROR;
DEBUGTRACE((LOG_ESS, "Pager: Received ACK on login\n"));
}
else
ERRORTRACE((LOG_ESS, "Pager: Failed to receive ACK on Login\n"));
return hr;
}
HRESULT CPagerSink::SendMessage(HANDLE hPort, BSTR message)
{
HRESULT hr = WBEM_E_FAILED;
if (ReadyToProceed(hPort))
{
DEBUGTRACE((LOG_ESS, "Pager: CPagerSink::SendMessage\n"));
// alloc enough buffer for message that's all double-byte chars.
// not that the pager is likely to UNDERSTAND, but let's not embarrass ourselves with an overwrite.
// also room for null teminator, the separators that TAP will want, and some paranoia-padding
// format: <STX>[pager id]<CR>[message]<ETX>[3 char checksum]<CR>
char* pMessage = new char[2*wcslen(message) + 2*wcslen(m_pagerID) +12];
if (!pMessage)
hr = WBEM_E_OUT_OF_MEMORY;
else
{
// djinn up the message per format
sprintf(pMessage, "\x02%S\x0D%S\x03", m_pagerID, message);
int nlen = strlen(pMessage);
int sum = 0;
// compute checksum:
for (int i = 0; i < nlen; i++)
sum += pMessage[i];
// only want the lower three bytes:
sum &= 0x0FFF;
// append to message:
// it's hex: but 'A' through 'E' are ':' through '?'
// (look at ASCII table - it makes sense)
// there's probably a cute way to do this in a loop,
// but what the heck, I'm lazy (or stoopid):
pMessage[nlen +0] = (char) (0x30 + ((sum & 0x0F00) / 0x100));
pMessage[nlen +1] = (char) (0x30 + ((sum & 0x00F0) / 0x10));
pMessage[nlen +2] = (char) (0x30 + ((sum & 0x000F) / 0x1));
pMessage[nlen +3] = '\x0D';
pMessage[nlen +4] = '\0';
// we'll retry if we get a NAK
DWORD bytesWrytten;
int nTries = 0;
do
{
DEBUGTRACE((LOG_ESS, "Pager: attempting send #%d\n", nTries +1));
if (WriteFile(hPort, pMessage, strlen(pMessage), &bytesWrytten, NULL))
hr = WBEM_S_NO_ERROR;
}
while (GotNAK(hPort) && (++nTries < 3));
delete[] pMessage;
} // if allocated memory
}// if ready to proceed
return hr;
}
// issue logout commands and hang up.
// won't bother listening for any response:
// we don't care - we were just leaving, anyway.
void CPagerSink::LogOut(HANDLE hPort)
{
DEBUGTRACE((LOG_ESS, "Pager: CPagerSink::LogOut\n"));
DWORD bytesWrytten;
char* logout = "\x04\r";
WriteFile(hPort, logout, strlen(logout), &bytesWrytten, NULL);
char* hangup = "ATH0";
WriteFile(hPort, hangup, strlen(hangup), &bytesWrytten, NULL);
}
HRESULT CPagerSink::RingMeUp(BSTR message)
{
DEBUGTRACE((LOG_ESS, "Pager: Paging with message \"%S\"\n", message));
HRESULT hr = WBEM_E_FAILED;
LPSTR port = m_port.GetLPSTR();
if (!port)
hr = WBEM_E_OUT_OF_MEMORY;
else
{
HANDLE hPort;
hPort = CreateFile(port, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
delete port;
port = NULL;
if (hPort == INVALID_HANDLE_VALUE)
{
ERRORTRACE((LOG_ESS, "Pager: CreateFile failed with %d\n", GetLastError()));
// todo: investigate various failure modes
// we ought to be able to distinguish between a modem that's in use
// and one that doesn't exist, at the very least.
hr = WBEM_E_FAILED;
// hr = WBEM_E_INVALID_PARAMETER;
}
else
{
DCB* pDCBOld = NULL;
if (SUCCEEDED(hr = ConfigureModem(hPort, &pDCBOld)) &&
SUCCEEDED(hr = DialUp(hPort)) &&
SUCCEEDED(hr = Login(hPort)))
{
hr = SendMessage(hPort, message);
}
// even if we blew it somewhere along the line
// we still want to hang up.
// at worst we'll be sending commands to a modem that's already disconnected
LogOut(hPort);
if (pDCBOld)
{
SetCommState(hPort, pDCBOld);
delete pDCBOld;
pDCBOld = NULL;
}
CloseHandle(hPort);
}
}
return hr;
}
void* CPagerSink::GetInterface(REFIID riid)
{
if (riid == IID_IWbemUnboundObjectSink)
return &m_XSink;
else
return NULL;
}