|
/*
RARPD.CPP:
Free software Copyright (c) 1999-2003 Lew Perin
*/
/*
Revision History:
Version Date Reason
------- --------
1.00 10/31/99 Forked from billgpc.cpp.
1.01 11/27/99 Lazy automatic reinitialization of the RARP table
when
it has been changed on disk, removal of lots of
chatter
from the log and main window.
1.02 1/17/00 Faster retrieval of IP address via qsort/bsearch.
1.03 6/3/00 Bugs in automatic reinitialization, handle usage,
error messages fixed; thanks, Ury Jamshy!
1.04 8/12/00 Fixed recognition of directory with embedded
spaces
from command line; thanks, Jiri Medlen!
1.05 9/17/00 Fixed TCP Registry navigation
for Windows 2000.
1.06 1/15/01 Made some minor type/constness changes to satisfy
modern C++ compilers. Fixed bug that could
destroy adapterName when excluding subnets.
1.07 5/17/01 In Win2K we now probe devices to see if they're
really there.
1.08 6/18/01 Minor debug logging changes.
1.09 7/23/01 Fixed bug recognizing Registry key for adapter
where one adapter is good and a subsequent one is
*almost* OK.
1.10 12/18/01 In Win2K/XP we now no longer check first for
direct
connection to Tcpip in checking for a useful
adapter.
1.10.1 3/3/02 We no longer assume a ("useless" non-physical)
adapter
will have an Ndi\Interfaces subkey. Temporarily,
we
ignore whether a 2K/XP adapter is connected to
Tcpip.
1.10.2 3/20/02 Now using new driver version for 2K/XP
compatibility.
Delay response slightly in loopback testing.
1.11 4/13/03 Logic for subnet exclusion now considers DHCP-based
Registry subnet parameters too.
1.12 4/20/03 Cleaned up logic for when checkStack()
fails.
1.13 4/29/03 Fixed getValue() length bug in getSubnetMask().
1.14 6/6/03 Now require driver version 1.02
1.15 10/12/03 Compute our IP address the Winsock way if the
Registry
fails us.
*/
#if !defined(_MT) // Symantec seems to need this to believe we're
multithreaded
//#define _MT
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <process.h>
#include <wincon.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h> // for sscanf; sorry!
#include <iostream.h>
#include <strstrea.h>
#include <fstream.h>
#include <iomanip.h>
#include <string.h>
#include <stdlib.h>
#include <winioctl.h>
#include <winsock.h>
#include <time.h>
#include "shared.h"
#include "resource.h"
#define VERSION "1.15"
#define TEST_VERSION 0 // if nonzero, annoying messagebox
#define WM_REALLY_CLOSE WM_APP
BOOL quiescing = FALSE; // set by GUI, obeyed by threads
/*
This class will create a list of pseudo-IP addresses for subnets from
a
RARPD command line and return them one by one with the overloaded
array
indexing operator, returning zero if you've gone beyond the last one.
*/
class SubnetHolder {
size_t count;
long* subnets;
public:
SubnetHolder() { count = 0; }
~SubnetHolder() { if (count) delete[] subnets; }
void init(char* acmdLine);
long operator[] (size_t a) { return (a < count) ? subnets[a] : 0; }
};
void SubnetHolder::init(char* acmdLine)
{
const char* sentinel = "/XS";
if (count) {
delete[] subnets;
count = 0;
}
for (char* next = acmdLine;
(next = strstr(next, sentinel)) != NULL;
count++) {
next += strlen(sentinel);
long* newSubnets = new long[count + 1];
newSubnets[count] = inet_addr(next);
if (count) {
memcpy(newSubnets, subnets, count * sizeof(long));
delete[] subnets;
}
subnets = newSubnets;
}
}
/*
What OS are we running on? Only NT's acceptable.
*/
enum OSType { W95, WNT };
OSType os = W95;
DWORD osMajorVersion, osMinorVersion;
UINT mbType = MB_APPLMODAL;
HDESK mainWindowDesktop = NULL; // used in NT desktop switching
/*
Navigating the registry:
*/
// Table listing info on legal root keys:
struct {
char* name;
HKEY handle;
} rootKeys[] = {
{ "HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT },
{ "HKEY_CURRENT_USER", HKEY_CURRENT_USER },
{ "HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE },
{ "HKEY_USERS", HKEY_USERS },
{ "HKEY_DYN_DATA", HKEY_DYN_DATA },
{ NULL, NULL }
};
/*
These are the names of the registry keys we use the most:
*/
char* tcpKeyName = ""; // filled in by configureForOS()
/*
We'll often need to know if a key is a root key because if so we'd
better
not close its handle.
*/
BOOL isRootKey(HKEY h)
{
for (int i = 0; rootKeys[i].name != NULL; i++) {
if (rootKeys[i].handle == h) return TRUE;
}
return FALSE;
}
/*
It's impossible to get a handle for a non-root key; you have to start
with its root key and work outward.
*/
HKEY getRootKey(const char* apath)
{
for (int i = 0; rootKeys[i].name != NULL; i++) {
size_t rkiLen = strlen(rootKeys[i].name);
if (!strncmp(apath, rootKeys[i].name, rkiLen)) {
if ((apath[rkiLen] == '\0') || (apath[rkiLen] == '\\')) {
return rootKeys[i].handle;
}
}
}
return NULL;
}
ofstream outFile;
enum {
logLineLen = 500, // Use this in ostrstreams for listbox
string.
msgLen = 500, // Use this for message boxes.
ObjectNameLen = logLineLen / 2 // Use this for desktop names.
};
/*
If we have two handles to NT desktops, the objects may be the same
even
if the handles are different. That's what we test here.
*/
BOOL differentObjects(HANDLE ahandleA, HANDLE ahandleB)
{
BOOL result = FALSE;
char objectNameA[ObjectNameLen];
char objectNameB[ObjectNameLen];
DWORD ignoreLenNeeded;
if ((GetUserObjectInformation(ahandleA, UOI_NAME, objectNameA,
ObjectNameLen, &ignoreLenNeeded)) &&
(GetUserObjectInformation(ahandleB, UOI_NAME, objectNameB,
ObjectNameLen, &ignoreLenNeeded))) {
result = strcmp(objectNameA, objectNameB);
}
else outFile << "Can't get object name" << GetLastError() << endl;
return result;
}
/*
Send the main window's listbox one line of text. After we send the
line
we select it to make sure it's visible, and then we deselect it so it
won't call attention to itself. If we're in a desperate hurry,
though -
dump() is, due to its responsibility in promiscuous mode - we do
without
the visibility manipulation. By the way, while this function would
run
faster using PostMessage() than with SendMessage(), the data wouldn't
get
through reliably.
There's a kink here that was introduced when we made it possible to
run,
launched by an NT service at boot time, trying to grab a desktop we
wouldn't
normally have (WinLogon.) Since we can't execute SetThreadDesktop
once
we already have a window, we need the ability to log messages without
a
main window, i.e. straight to rarpd.log.
*/
void logLine(HWND amainWindow, const char* aline, BOOL ahurry = FALSE)
{
if (!amainWindow) {
outFile << aline << endl;
return;
}
static HWND dlg = HWND(INVALID_HANDLE_VALUE);
if (dlg == INVALID_HANDLE_VALUE) dlg = GetDlgItem(amainWindow,
IDC_LOGBOX);
SendMessage(dlg, LB_ADDSTRING, 0, (LPARAM)aline);
if (!ahurry) {
LONG lastLine = SendMessage(dlg, LB_GETCOUNT, 0, 0) - 1;
SendMessage(dlg, LB_SETCARETINDEX, WPARAM(lastLine),
MAKELPARAM(FALSE, 0));
UpdateWindow(dlg);
}
}
/*
This is a convenience function for the cases where we just have a
string
to put out together with Windows's wisdom on what might have happened.
Just to be tidy, we strip the trailing CRLF in the Windows error
message.
*/
void logLastError(HWND amainWindow, char* aline)
{
char* errMsgBuf = "";
char* msgBuf = new char[logLineLen];
ostrstream msgOss(msgBuf, logLineLen);
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default
language
(LPTSTR) &errMsgBuf, 0, NULL);
char* crLoc = strchr(errMsgBuf, '\r');
if (crLoc) *crLoc = '\0';
msgOss << aline << ": " << errMsgBuf << ends;
logLine(amainWindow, msgBuf);
LocalFree(errMsgBuf);
}
/*
Running under NT, launched by a service, things get complicated.
Originally
our aproach involved a window of 0 to go along with our
MB_APPLMODAL | MB_SERVICE_NOTIFICATION style. But we like to make the
whole context of the rarp transaction, i.e. the log in our main
window,
available when things get bad enough for us to put up a message box.
So
we devised a scheme that allowed us to switch desktops temporarily
back
to the one our main window lives in, and then restore the current
input
desktop once the message box is dismissed. If this seems bizarre,
consider
that the situation really does arise when the following events
transpire:
- rarpd is launched by its service before anyone logs on and puts its
main window where it can be seen, i.e. in the Winlogon desktop;
- someone logs on before rarpd finishes, so the Default desktop
becomes
current and rarpc's main window becomes invisible;
- rarpd learns of something that requires the user's attention and
wants
to put up a message box anchored by the main window.
By the way, there's plenty of time for the second event in this
sequence
to interpose itself between the first and third when the third is a
rarp
failure, i.e. a timeout.
*/
int ourMessageBox(HWND hwnd, char* amsg, UINT atype)
{
int result;
ShowWindow(hwnd, SW_RESTORE);
if (os == WNT) {
HDESK newDesktop = OpenInputDesktop(0, FALSE,
DESKTOP_SWITCHDESKTOP);
if (!newDesktop) logLastError(hwnd, "Can't open input desktop");
if (differentObjects(mainWindowDesktop,
GetThreadDesktop(GetCurrentThreadId()))) {
if (!SetThreadDesktop(mainWindowDesktop)) {
logLastError(hwnd, "SetThreadDesktop for main window");
}
}
if ((mainWindowDesktop && newDesktop) &&
differentObjects(mainWindowDesktop, newDesktop)) {
if (SwitchDesktop(mainWindowDesktop)) {
result = MessageBox(hwnd, amsg, "rarpd", atype);
if (!result) logLastError(hwnd, "MessageBox");
if (!SwitchDesktop(newDesktop)) {
logLastError(hwnd, "Couldn't switch back to new desktop");
}
return result;
}
else logLastError(hwnd, "Couldn't switch to old desktop");
} // else fall through
} // end of NT-specific logic
result = MessageBox(hwnd, amsg, "rarpd", atype);
if (!result) logLastError(hwnd, "MessageBox");
return result;
}
/*
Two functions, alert() and fail(), centralize our error messages
and ensure that when an error message goes up on the screen the
listbox
is visible so the user has some context for the message. A third,
yesOrNo(), does something similar when the user must decide. These
functions also make sure messages and responses get logged.
*/
/*
Here we've reached a point where we can't go on. This is a
convenience
function removing some Windows GUI clutter. For those cases in which
we think it's OK without alarming the user, there's an optional
Boolean
allowing this.
*/
void fail(HWND amainWindow, char* amsg, BOOL asilent = FALSE)
{
if (!asilent) ourMessageBox(amainWindow, amsg, MB_ICONSTOP);
logLine(amainWindow, "**Fatal error:");
logLine(amainWindow, amsg);
if (amainWindow) SendMessage(amainWindow, WM_CLOSE, 0, 0);
else exit(-1);
}
/*
Here we need to alert the user to something that might not be fatal.
*/
void alert(HWND amainWindow, char* amsg)
{
ourMessageBox(amainWindow, amsg, MB_ICONEXCLAMATION);
logLine(amainWindow, amsg);
}
/*
Here we need to ask the user a yes-or-no question.
*/
int yesOrNo(HWND amainWindow, char* amsg)
{
int result;
result = ourMessageBox(amainWindow, amsg, MB_YESNO);
logLine(amainWindow, "**Query:");
logLine(amainWindow, amsg);
logLine(amainWindow, ((result == IDYES) ? "[yes]" : "[no]"));
return result;
}
/*
This class will create a list RARP table entries from rarpd.tbl
and return pointers to them one by one with the overloaded array
indexing operator, returning NULL if you've gone beyond the last one.
*/
struct RarpTblEntry {
UCHAR macAddress[MacAddressSize];
UINT ipAddress;
};
int rarpTblEntryCmp(const void* aleft, const void* aright)
{
const RarpTblEntry* left = (const RarpTblEntry*)aleft;
const RarpTblEntry* right = (const RarpTblEntry*)aright;
return memcmp(left->macAddress, right->macAddress, MacAddressSize);
}
/*
Our RARP table class encapsulates a lazy reinitialization behavior in
response to the table having changed on disk. We only consider
reinitializing the table when the caller is accessing the first entry
in the table; then we check the file only if it has been at least a
minute since the last check. We reinitialize then if the file has
been
written since the last initialization.
*/
class RarpTbl {
size_t count;
RarpTblEntry* entries;
HWND mainWindow;
time_t timeOfLastFileCheck;
FILETIME timeOfLastInit;
void considerInit();
public:
RarpTbl(HWND amainWindow) : mainWindow(amainWindow), count(0),
timeOfLastFileCheck(0) {
timeOfLastInit.dwLowDateTime = timeOfLastInit.dwHighDateTime = 0;
init();
}
~RarpTbl() { if (count) delete[] entries; }
void init();
RarpTblEntry* operator[] (size_t a) {
if (a == 0) considerInit();
return (a < count) ? &entries[a] : NULL;
}
RarpTblEntry* find(const unsigned char* akey) {
considerInit();
return (RarpTblEntry*)bsearch(akey, entries, count,
sizeof(RarpTblEntry),
rarpTblEntryCmp);
}
};
void RarpTbl::considerInit()
{
time_t now = time(NULL);
if (UINT(now - timeOfLastFileCheck) > 60) {
timeOfLastFileCheck = now;
HANDLE fh = CreateFile("RARPD.TBL", GENERIC_READ,
0, // Not interested if it's being edited
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
NULL);
if (fh != INVALID_HANDLE_VALUE) {
FILETIME timeLastWritten;
BOOL gotFileTime = GetFileTime(fh, NULL, NULL, &timeLastWritten);
CloseHandle(fh);
if (gotFileTime) {
if (CompareFileTime(&timeOfLastInit, &timeLastWritten) < 0)
init();
}
}
}
}
void RarpTbl::init()
{
GetSystemTimeAsFileTime(&timeOfLastInit);
ifstream inFile("RARPD.TBL", ios::in | ios::nocreate);
logLine(mainWindow, "Initializing RARP table");
if (!inFile.ipfx()) fail(mainWindow, "Can't open RARPD.TBL");
if (count) {
delete[] entries;
count = 0;
}
char buf[200];
for (size_t lineNo = 1; inFile.ipfx(); lineNo++) {
RarpTblEntry next;
next.ipAddress = INADDR_NONE;
inFile.getline(buf, sizeof(buf));
// logLine(mainWindow, buf);
if (!isalnum(buf[0])) continue;
char ipAddr[200];
int digits[MacAddressSize];
if (sscanf(buf, "%02x.%02x.%02x.%02x.%02x.%02x %s",
digits, digits + 1, digits + 2, digits + 3, digits + 4,
digits + 5, ipAddr) == 7) {
for (size_t byteNo = 0; byteNo < MacAddressSize; byteNo++) {
next.macAddress[byteNo] = UCHAR(digits[byteNo]);
}
next.ipAddress = inet_addr(ipAddr);
}
if (next.ipAddress == INADDR_NONE){
strcat(buf, ": bad");
logLine(mainWindow, buf);
continue;
}
RarpTblEntry* newEntries = new RarpTblEntry[count + 1];
memcpy(&newEntries[count], &next, sizeof(RarpTblEntry));
if (count) {
memcpy(newEntries, entries, count * sizeof(RarpTblEntry));
delete[] entries;
}
entries = newEntries;
count++;
}
qsort(entries, count, sizeof(RarpTblEntry), rarpTblEntryCmp);
}
/*
Here, since we know we're running on NT, we do what's necessary to
make
ourselves useful should we be running launched by a service before
logon.
(This is after all the best way to run rarpd on NT.) We see if we're
already running on the input desktop and, if not, try to seize it. If
we do switch desktops we make sure our window won't be minimized,
because
in our experience a minimized window on the NT Winlogon desktop is a
dead
duck.
*/
void setNTDesktop(int& acmdShow, const OSVERSIONINFO& avInfo)
{
char* msgBuf = new char[logLineLen];
ostrstream msgOss(msgBuf, logLineLen);
mbType = (avInfo.dwMajorVersion < 4) ? // Service notification
MB_TASKMODAL | 0x00040000 : MB_TASKMODAL | 0x00200000;
enum { ObjectNameLen = logLineLen / 2 };
char oldDesktopName[ObjectNameLen];
char newDesktopName[ObjectNameLen];
DWORD ignoreLenNeeded;
HWINSTA windowStation = GetProcessWindowStation();
if (windowStation) {
char stationName[ObjectNameLen];
if (GetUserObjectInformation(windowStation, UOI_NAME, stationName,
ObjectNameLen, &ignoreLenNeeded)) {
msgOss << "Window station name: " << stationName << ends;
logLine(0, msgBuf);
msgOss.seekp(0);
}
else logLastError(0, "Can't get window station name");
}
else logLastError(0, "Can't get window station");
HDESK oldDesktop = GetThreadDesktop(GetCurrentThreadId());
if (oldDesktop) {
if (GetUserObjectInformation(oldDesktop, UOI_NAME, oldDesktopName,
ObjectNameLen, &ignoreLenNeeded)) {
msgOss << "Old desktop name: " << oldDesktopName << ends;
logLine(0, msgBuf);
msgOss.seekp(0);
}
else logLastError(0, "Can't get old desktop name");
}
else logLastError(0, "Can't get thread desktop");
HDESK newDesktop = OpenInputDesktop(0, TRUE, DESKTOP_CREATEWINDOW |
DESKTOP_SWITCHDESKTOP);
if (newDesktop) {
if (GetUserObjectInformation(newDesktop, UOI_NAME, newDesktopName,
ObjectNameLen, &ignoreLenNeeded)) {
msgOss << "New desktop name: " << newDesktopName << ends;
logLine(0, msgBuf);
msgOss.seekp(0);
}
else logLastError(0, "Can't get new desktop name");
if (strcmp(oldDesktopName, newDesktopName)) {
if (SetThreadDesktop(newDesktop)){
acmdShow = SW_SHOWNORMAL;
mainWindowDesktop = newDesktop;
}
else logLastError(0, "SetThreadDesktop");
}
else {
mainWindowDesktop = oldDesktop;
logLine(0, "Already have the input desktop");
}
}
else logLastError(0, "Can't open input desktop");
}
/*
The handles for dealing with the NT RARP driver:
*/
SC_HANDLE scmHandle = NULL;
SC_HANDLE srvHandle = NULL;
HANDLE ntRarpHandle = INVALID_HANDLE_VALUE;
HINSTANCE ourInstance = HINSTANCE(INVALID_HANDLE_VALUE);
/*
Normally the driver doesn't already exist as a service when we're
called,
but if it already exists that's OK. We know the current directory has
been
set to the directory rarpd was loaded from. The call to
GetFileAttributes
is necessary because CreateService will vacuously succeed even if it
fails
to find the driver.
*/
BOOL installNTDriver(SC_HANDLE ascmHandle)
{
BOOL result = FALSE;
char driverPath[logLineLen];
*driverPath = 0;
GetCurrentDirectory(logLineLen, driverPath);
wsprintf(driverPath + strlen(driverPath), "\\RARP.SYS");
if (GetFileAttributes(driverPath) != 0xffffffff) {
SC_HANDLE srvHandle =
CreateService(ascmHandle, "RARP", "RARP Support",
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
driverPath,
NULL, NULL, NULL, NULL, NULL);
if (srvHandle == NULL) {
if (GetLastError() == ERROR_SERVICE_EXISTS) {
logLine(0, "RARP.SYS already existed");
result = TRUE;
}
else logLastError(0, "Couldn't install RARP.SYS");
}
else {
logLine(0, "Created service for RARP.SYS");
result = TRUE;
CloseServiceHandle(srvHandle); // 0.54.2
}
}
else logLine(0, "Can't find RARP.SYS");
return result;
}
/*
Here we wait for success (in which case we return TRUE) or either of
two
kinds of failure:
- we get an error from StartService() other than an indication that
the
service database is still locked;
- the user gives up.
In either error condition we log an error message and return FALSE.
*/
BOOL APIENTRY startServiceDialogProc(HWND hDlg, UINT message, UINT
wParam,
LONG /* lParam */)
{
switch (message)
{
case WM_INITDIALOG:
{
SetDlgItemText(hDlg, IDC_EDIT_WAIT_SCM,
"Windows NT has reported to rarpd that the "
"service database is locked. This means that "
"rarpd's device driver cannot yet be started. "
"Normally this is "
"a temporary situation, especially when rarpd "
"is being used to configure a new
workstation.\r\n"
"Unless you press the button below, rarpd will "
"continue trying to start its driver until it "
"succeeds.\r\n"
"If you press the button, rarpd will try to reach
"
"the rarp server without the use of its device "
"driver. You are strongly urged to wait.");
SetTimer(hDlg, 0, 5000, NULL);
}
break;
case WM_COMMAND:
if (wParam == IDC_BUTTON_STOP) {
SetLastError(ERROR_SERVICE_DATABASE_LOCKED);
logLastError(0, "Couldn't start RARP service");
return EndDialog(hDlg, FALSE);
}
break;
case WM_TIMER:
if (StartService(srvHandle, 0, NULL)) return EndDialog (hDlg,
TRUE);
else {
DWORD error = GetLastError();
if (error != ERROR_SERVICE_DATABASE_LOCKED) {
SetLastError(error);
logLastError(0, "Couldn't start RARP service");
return EndDialog (hDlg, FALSE);
}
}
break;
}
return FALSE;
}
/*
Because our call to StartService() can be frustrated by a transitory
lock
on the service database held by some other process, we keep trying to
start our driver periodically while displaying a dialog that allows
the
user to give up.
*/
void ourStartService()
{
BOOL result = FALSE;
if (StartService(srvHandle, 0, NULL)) result = TRUE;
else {
DWORD error = GetLastError();
if (error == ERROR_SERVICE_DATABASE_LOCKED) {
result = DialogBox(ourInstance, MAKEINTRESOURCE(DIALOG_WAIT_SCM),
0,
(DLGPROC)startServiceDialogProc);
}
else {
SetLastError(error);
logLastError(0, "Couldn't start RARP service");
}
}
if (result) logLine(0, "Started RARP service");
}
void connectToNTDriver()
{
scmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (scmHandle == NULL) fail(0, "OpenSCManager");
if (installNTDriver(scmHandle)) {
srvHandle = OpenService(scmHandle, "RARP", SERVICE_ALL_ACCESS);
if (srvHandle != NULL) ourStartService();
else logLastError(0, "Couldn't open RARP service");
}
}
void disconnectFromNTDriver()
{
if (ntRarpHandle != INVALID_HANDLE_VALUE) {
if (!CloseHandle(ntRarpHandle)) {
logLastError(0, "Can't close RARP device");
}
}
if (srvHandle != NULL) {
SERVICE_STATUS serviceStatus;
SC_HANDLE stopHandle = OpenService(scmHandle, "RARP",
SERVICE_ALL_ACCESS);
if (stopHandle == NULL) logLastError(0, "OpenService stopping
RARP");
else {
if (!ControlService(stopHandle, SERVICE_CONTROL_STOP,
&serviceStatus)) {
logLastError(0, "ControlService SERVICE_STOP");
}
CloseServiceHandle(stopHandle);
}
SC_HANDLE removeHandle = OpenService(scmHandle, "RARP",
SERVICE_ALL_ACCESS);
if (removeHandle == NULL) logLastError(0, "OpenService removing
RARP");
else {
if (!DeleteService(removeHandle)) {
logLastError(0, "DeleteService for RARP");
}
CloseServiceHandle(removeHandle);
}
CloseServiceHandle(srvHandle);
}
if (scmHandle != NULL) CloseServiceHandle(scmHandle);
}
void shutdown()
{
if (os == WNT) disconnectFromNTDriver();
char dateStr[80], timeStr[80];
GetDateFormat(LOCALE_USER_DEFAULT, 0, NULL,
"yyyy'-'MM'-'dd", dateStr, sizeof(dateStr));
GetTimeFormat(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT, NULL,
"HH':'mm':'ss", timeStr, sizeof(timeStr));
outFile << "rarpd finished " << dateStr << " " << timeStr << endl;
outFile.close();
}
BOOL ctrlHandler(DWORD actrlChar)
{
switch(actrlChar)
{
case CTRL_SHUTDOWN_EVENT:
case CTRL_LOGOFF_EVENT:
shutdown();
return FALSE; // Quit the program.
default:
break;
}
return TRUE; // Don't quit.
}
/*
Since we run under two different operating systems, there are some
things
we need to set up depending on which one it is. We keep an enumerator
for the OSes we tolerate.
*/
DWORD transportValueType = REG_SZ;
char* subnetMaskString = "IPMask";
char* dhcpSubnetMaskString = "DhcpIPMask";
void configureForOS(int& acmdShow)
{
char* msgBuf = new char[logLineLen];
ostrstream msgOss(msgBuf, logLineLen);
char* osString = "";
OSVERSIONINFO vinfo;
vinfo.dwOSVersionInfoSize = sizeof(vinfo);
GetVersionEx(&vinfo);
osMajorVersion = vinfo.dwMajorVersion;
osMinorVersion = vinfo.dwMinorVersion;
switch(vinfo.dwPlatformId) {
case VER_PLATFORM_WIN32_NT:
os = WNT;
osString = "NT";
tcpKeyName = "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\"
"Services\\Tcpip\\Parameters";
transportValueType = REG_MULTI_SZ;
subnetMaskString = "SubnetMask";
dhcpSubnetMaskString = "DhcpSubnetMask";
setNTDesktop(acmdShow, vinfo);
#if 0 // Control handler doesn't get called at system shutdown: why??
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)ctrlHandler, TRUE)) {
fail(0, "Can't install shutdown handler");
}
#endif
connectToNTDriver();
break;
default:
fail(0, "This program requires Windows NT");
break;
}
msgOss << "Running under Windows " << osString << " "
<< osMajorVersion << "." << osMinorVersion << "." << ends;
logLine(0, msgBuf);
}
/*
Here we can't proceed because a registry key we need doesn't exist.
*/
void abortOnBadKey(const char* apath, HWND amainWindow)
{
char* msgBuf = new char[logLineLen];
ostrstream msgOss(msgBuf, logLineLen);
msgOss << "Can't find key" << endl
<< " " << apath << endl
<< "in registry.\n"
<< "Make sure Microsoft TCP/IP is properly installed"
<< " and that you have Registry write access for IP."
<< ends;
fail(amainWindow, msgBuf);
}
/*
As we navigate outward from the root to the key we seek, we make sure
to
close all intermediate keys. While navigating we ask only read
access
but at our destination we want to be able to modify things.
*/
HKEY getKey(const char* apath, HWND amainWindow, BOOL asilent = FALSE)
{
HKEY thisKeyHandle = getRootKey(apath);
if (thisKeyHandle == NULL) abortOnBadKey(apath, amainWindow);
char subKeyName[80];
const char* separatorP = apath + strcspn(apath, "\\");
while (*separatorP) {
const char* nextSubKeyP = separatorP + 1;
size_t subKeyLen = strcspn(nextSubKeyP, "\\");
memcpy(subKeyName, nextSubKeyP, subKeyLen);
subKeyName[subKeyLen] = '\0';
separatorP = nextSubKeyP + subKeyLen;
HKEY nextKeyHandle;
if (RegOpenKeyEx(thisKeyHandle, subKeyName, 0,
KEY_READ,
&nextKeyHandle)!= ERROR_SUCCESS) {
if (!asilent) {
char* msgBuf = new char[logLineLen];
ostrstream msgOss(msgBuf, logLineLen);
msgOss << "Failure on " << apath << " at subkey: " <<
subKeyName <<
" with read access." << ends;
logLine(amainWindow, msgBuf);
}
RegCloseKey(thisKeyHandle);
return NULL;
}
RegCloseKey(nextKeyHandle);
if (RegOpenKeyEx(thisKeyHandle, subKeyName, 0,
KEY_READ | KEY_SET_VALUE,
&nextKeyHandle)!= ERROR_SUCCESS) {
if (!asilent) {
char* msgBuf = new char[logLineLen];
ostrstream msgOss(msgBuf, logLineLen);
msgOss << "Failure on " << apath << " at subkey: " <<
subKeyName <<
" with full access." << ends;
logLine(amainWindow, msgBuf);
}
RegCloseKey(thisKeyHandle);
return NULL;
}
if (!isRootKey(thisKeyHandle)) RegCloseKey(thisKeyHandle);
thisKeyHandle = nextKeyHandle;
}
return thisKeyHandle;
}
/*
Sometimes we just need to know if a Registry key exists...
*/
BOOL keyExists(const char* apath, HWND amainWindow)
{
BOOL result = FALSE;
HKEY keyHandle = getKey(apath, amainWindow, TRUE);
if (keyHandle != NULL) {
RegCloseKey(keyHandle);
result = TRUE;
}
return result;
}
/*
If we can find a value corresponding to the key name and expected
name,
return TRUE and stow its length. The argument "alength" is used a la
Microsoft, i.e. both as input and output. We don't leave a non-root
key handle open.
*/
BOOL getValue(const char* akeyName, char* anexpectedName, BYTE* abuf,
DWORD& alength, DWORD atype, HWND amainWindow)
{
HKEY keyHandle = getKey(akeyName, amainWindow);
BOOL result = FALSE;
DWORD regType; // value's type
if (keyHandle == NULL) abortOnBadKey(akeyName, amainWindow);
if (RegQueryValueEx(keyHandle, anexpectedName, NULL,
®Type, abuf, &alength) == ERROR_SUCCESS) {
if (regType == atype) result = TRUE;
else {
size_t bufLen = strlen(anexpectedName) + strlen(akeyName) + 100;
char* msgBuf = new char[bufLen];
ostrstream msgOss(msgBuf, bufLen);
msgOss << "Unexpected value type in registry for" << endl
<< " key: " << akeyName << endl
<< " value name: " << anexpectedName << ends;
alert(amainWindow, msgBuf);
}
}
if (!isRootKey(keyHandle)) RegCloseKey(keyHandle);
return result;
}
/*
The timeout that the receiver thread itself uses to quit on a read
from
RARP is made shorter than the one the worker thread uses to kill the
receiver thread by the initialization of ReceiverParams.
*/
struct ReceiverParams {
HWND mainWindow;
DWORD timeout;
UINT ourIpAddress;
BOOL& quiescing;
UCHAR ourMacAddress[MacAddressSize];
USHORT ourHwType;
UCHAR ourHwLen;
RarpTbl& tbl;
ReceiverParams(HWND amainWindow, DWORD atimeout, RarpTbl& atbl,
UINT anipAddress, BOOL& aquiescing) :
mainWindow(amainWindow),
timeout((atimeout > 2000) ? (atimeout - 2000) : 1000 ),
tbl(atbl), ourIpAddress(anipAddress), quiescing(aquiescing) { }
BOOL getntRarpHardwareAddress();
};
struct WorkerParams {
HWND mainWindow;
BOOL dumpDriver; // dump the driver?
SubnetHolder& excludedSubnets; // Subnets we ignore searching for
netcard
DWORD rxTimeout; // how long we wait for reply (msec.)
char* transportKeyName; // card-specific TCP transport key name
char* adapterName; // e.g. "Elnk31"
WorkerParams(HWND amainWindow,
BOOL adumpDriver, DWORD arxTimeout,
SubnetHolder& anexcludedSubnets) :
mainWindow(amainWindow), dumpDriver(adumpDriver),
rxTimeout(arxTimeout),
excludedSubnets(anexcludedSubnets),
transportKeyName(NULL), adapterName(NULL) { };
~WorkerParams() {
if (transportKeyName) delete transportKeyName;
if (adapterName) delete adapterName;
};
void dumpDriverMemory();
char* findNT4NetcardTCPParams();
char* findNT5NetcardTCPParams();
BOOL isSubnetExcluded(char* atcpParamsKeyName);
void openntRarp();
BOOL checkStack();
UINT ourIpAddress();
BOOL probeAdapter(char* anadapterName);
};
/*
If we get something from the netcard that looks fishy, we want to
dump it.
*/
void dump(HWND amainWindow, UINT asize, BYTE* abufP, char* alegend)
{
logLine(amainWindow, alegend);
for (UINT offset = 0; offset < asize; offset += 16) {
char* msgBuf = new char[logLineLen];
ostrstream msgOss(msgBuf, logLineLen);
msgOss << hex << setw(3) << setfill('0') << offset << ":";
for (UINT incr = 0; incr < 16; incr++) {
if (offset + incr >= asize) break;
if ((incr % 4) == 0) msgOss << " ";
msgOss << hex << setw(2) << setfill('0') << WORD(abufP[offset +
incr]);
}
msgOss << ends;
logLine(amainWindow, msgBuf, TRUE);
}
}
BOOL ReceiverParams::getntRarpHardwareAddress()
{
char allZeroes[6] = { 0 };
DWORD length;
BYTE hwType;
BOOL result = FALSE;
if (DeviceIoControl(ntRarpHandle, DIOC_RarpGetMacAddress,
NULL, 0, ourMacAddress, MacAddressSize, &length,
NULL) &&
memcmp(ourMacAddress, allZeroes, sizeof(allZeroes))) {
DWORD hwLength;
if (DeviceIoControl(ntRarpHandle, DIOC_RarpGetHWType,
NULL, 0, &hwType, 1, &hwLength, NULL)) {
result = TRUE;
ourHwLen = UCHAR(length);
ourHwType = htons(hwType);
}
}
return result;
}
/*
The receiver thread exits when it detects a good reply
(params->success TRUE) or when the socket fails (FALSE.)
*/
void rarpReceiver(void* aparams)
{
ReceiverParams& params = *((ReceiverParams*)aparams);
if (!params.getntRarpHardwareAddress()) {
fail(params.mainWindow, "Couldn't get hardware address.");
}
RarpBuf rxBuf, txBuf;
txBuf.ap.hwType = params.ourHwType;
txBuf.ap.protType = htons(ProtocolTypeIP);
txBuf.ap.hwLen = params.ourHwLen;
txBuf.ap.protLen = 4;
txBuf.ap.op = htons(RarpOpReply);
memcpy(txBuf.ap.senderMacAddress, params.ourMacAddress,
MacAddressSize);
txBuf.ap.senderProtAddress = params.ourIpAddress;
while (!params.quiescing) {
OVERLAPPED ovrlp = {0,0,0,0,0};
DWORD bytesReturned;
BOOL gotIt = FALSE;
if ((ovrlp.hEvent = CreateEvent(0, FALSE, 0, NULL)) == 0) {
fail(params.mainWindow, "Windows is out of resources.");
}
else if (ReadFile(ntRarpHandle, &rxBuf, sizeof(RarpBuf),
&bytesReturned, &ovrlp)) gotIt = TRUE;
else if (GetLastError() == ERROR_IO_PENDING) {
while (!gotIt && !params.quiescing) {
if (WaitForSingleObject(ovrlp.hEvent, params.timeout)
== WAIT_OBJECT_0) {
GetOverlappedResult(ntRarpHandle, &ovrlp, &bytesReturned,
FALSE);
gotIt = TRUE;
}
}
}
else {
logLastError(params.mainWindow, "Can't read from RARP");
CloseHandle(ovrlp.hEvent);
break;
}
if (gotIt) { // It's for us.
if (ntohs(rxBuf.ap.op) == RarpOpRequest) {
memcpy(txBuf.ap.targetMacAddress, rxBuf.ap.targetMacAddress,
MacAddressSize);
RarpTblEntry* entryP =
params.tbl.find(rxBuf.ap.targetMacAddress);
if (entryP) {
txBuf.ap.targetProtAddress = entryP->ipAddress;
memcpy(txBuf.remoteMacAddress, rxBuf.remoteMacAddress,
MacAddressSize);
if (memcmp(params.ourMacAddress, rxBuf.remoteMacAddress,
MacAddressSize) == 0) { // loopback testing!
Sleep(50); // Give client time to dump its own
request.
}
OVERLAPPED txOvrlp = { 0 };
DWORD bytesSent;
if ((txOvrlp.hEvent = CreateEvent(0, FALSE, 0, NULL)) == 0) {
fail(params.mainWindow, "Can't create event for WriteFile");
}
else if (!WriteFile(ntRarpHandle, &txBuf, sizeof(txBuf),
&bytesSent, &txOvrlp)) {
if (GetLastError() == ERROR_IO_PENDING) {
GetOverlappedResult(ntRarpHandle, &txOvrlp, &bytesSent,
TRUE);
}
else logLastError(params.mainWindow, "WriteFile");
}
CloseHandle(txOvrlp.hEvent);
}
}
else if (ntohs(rxBuf.ap.op) != RarpOpReply) {
dump(params.mainWindow, bytesReturned, (UCHAR*)&rxBuf,
"Buffer but not a RARP request");
}
}
CloseHandle(ovrlp.hEvent);
}
_endthread();
}
/*
This function returns the last segments of a Registry key, starting
with
the character after the backslash, if there is a backslash. Usually
we
just want the last one segment, but we may want more than that.
*/
char* keyNameTail(char* akeyName, int atailSegments = 1)
{
char* p;
for (p = akeyName + strlen(akeyName) - 1; ; p--) {
if (p == akeyName) return p; // no backslash; simple keyname
if ((*p == '\\') && (--atailSegments <= 0)) break;
}
return (p + 1);
}
/*
Here we log everything we wrote to the listbox.
*/
void writeLog(HWND hDlg)
{
enum { lineBufLen = 500 };
char* lineBuf = new char[lineBufLen];
LRESULT lineLen;
for (USHORT lineNo = 0;
(lineLen = SendDlgItemMessage(hDlg, IDC_LOGBOX, LB_GETTEXTLEN,
lineNo, 0)) != LB_ERR;
lineNo++) {
if (lineLen < lineBufLen) {
SendDlgItemMessage(hDlg, IDC_LOGBOX, LB_GETTEXT, lineNo,
(LPARAM)lineBuf);
lineBuf[lineLen] = '\0';
outFile << lineBuf << endl;
}
else outFile << "**Listbox line too long: " << lineLen << endl;
}
delete lineBuf;
}
void WorkerParams::openntRarp()
{
enum { SYSVersionMin = 102, SYSVersionMax = 102 };
if (srvHandle != NULL) { // Service exists
char rarpDeviceName[80];
wsprintf(rarpDeviceName, "\\\\.\\RARP%s", adapterName);
char openMsg[120];
wsprintf(openMsg, "Opening %s", rarpDeviceName);
logLine(mainWindow, openMsg);
ntRarpHandle = CreateFile(rarpDeviceName,
GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED,
NULL);
if (ntRarpHandle != INVALID_HANDLE_VALUE) {
logLine(mainWindow, "Opened RARP device");
UINT sysVersion;
DWORD bytesReturned;
if (DeviceIoControl(ntRarpHandle, DIOC_RarpGetVersion,
NULL, 0, &sysVersion, sizeof(sysVersion),
&bytesReturned, NULL)) {
if ((sysVersion < SYSVersionMin) || (sysVersion >
SYSVersionMax)) {
logLine(mainWindow, "Wrong version of RARP.SYS");
CloseHandle(ntRarpHandle);
ntRarpHandle = INVALID_HANDLE_VALUE;
}
else {
char* msgBuf = new char[logLineLen];
ostrstream msgOss(msgBuf, logLineLen);
msgOss << "Using RARP.SYS version " <<
(sysVersion / 100) << "." << setw(2) << setfill('0') <<
(sysVersion % 100) << ends;
logLine(mainWindow, msgBuf);
}
}
else {
logLine(mainWindow, "Can't get version of RARP.SYS");
CloseHandle(ntRarpHandle);
ntRarpHandle = INVALID_HANDLE_VALUE;
}
}
else logLastError(mainWindow, "Opening RARP device");
}
}
/*
Can we find the subnet for this TCP parameters key among those
excluded?
*/
long getSubnetMask(char* atcpParamsKeyName, HWND amainWindow)
{
char buf[80];
long addr;
long result = 0;
DWORD addrLength = sizeof(buf), maskLength = sizeof(buf);
if (!getValue(atcpParamsKeyName, "IPAddress", (BYTE*)buf, addrLength,
REG_MULTI_SZ, amainWindow)) {
fail(amainWindow, "No IPAddr for TCP params key");
}
addr = inet_addr(buf);
if (addr != 0) { // static or BOOTP
logLine(amainWindow, "...static IP"); // ***temp
if (!getValue(atcpParamsKeyName, subnetMaskString, (BYTE*)buf,
maskLength,
REG_MULTI_SZ, amainWindow)) {
fail(amainWindow, "No Subnet Mask for TCP params key");
}
result = inet_addr(buf) & addr;
}
else { // DHCP
addrLength = sizeof(buf);
if (getValue(atcpParamsKeyName, "DhcpIPAddress", (BYTE*)buf,
addrLength,
REG_SZ, amainWindow)) {
addr = inet_addr(buf);
logLine(amainWindow, "...DHCP IP"); // ***temp
maskLength = sizeof(buf);
if (!getValue(atcpParamsKeyName, dhcpSubnetMaskString,
(BYTE*)buf, maskLength, REG_SZ, amainWindow)) {
fail(amainWindow, "No DHCP Subnet Mask for TCP params key");
}
result = inet_addr(buf) & addr;
}
}
char maskMsg[80]; // ***temp
in_addr mask; // ***temp
mask.S_un.S_addr = result; // ***temp
sprintf(maskMsg, "...mask = %s", inet_ntoa(mask)); // ***temp
logLine(amainWindow, maskMsg); // ***temp
return result;
}
BOOL WorkerParams::isSubnetExcluded(char* atcpParamsKeyName)
{
long mask = getSubnetMask(atcpParamsKeyName, mainWindow);
for (size_t subnetNo = 0;
(excludedSubnets[subnetNo]) != 0;
subnetNo++) {
if (excludedSubnets[subnetNo] == mask) {
logLine(mainWindow, "...excluded");
return TRUE;
}
}
return FALSE;
}
/*
Windows NT TCP Registry navigation, pre-NT 5:
The "Bind" value of the Tcpip Linkage key has a series of strings of
the
form \Device\adaptername, where if adaptername begins with "NdisWan"
it's
a Dial-up pseudo-adapter. We want one entry to remain. If so, that
adapter's Parameters\Tcpip key name is what we return.
While we're at it we also allocate and fill adapterName so we can find
the MAC address later.
The string we return is the caller's responsibility to delete.
*/
char* WorkerParams::findNT4NetcardTCPParams()
{
char* tcpLinkageKeyName =
"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\"
"Services\\Tcpip\\Linkage";
char* srvKeyPrefix = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\"
"Services\\";
char* tcpParamSuffix = "\\Parameters\\Tcpip";
DWORD bindBufLen = 1000;
char* bindBuf = new char[bindBufLen];
if (!getValue(tcpLinkageKeyName, "Bind", (BYTE*)bindBuf, bindBufLen,
REG_MULTI_SZ, mainWindow)){
fail(mainWindow, "Registry failure");
}
size_t nTCPKeys = 0; // Want to end up with 1
char* result = "";
for (CHAR* thisValue = bindBuf;
*thisValue;
thisValue += (strlen(thisValue) + 1)) {
if (strstr(thisValue, "NdisWan")) continue; // Not interested in
Dial-up!
char* resultSave = result;
result = new char[strlen(srvKeyPrefix) +
strlen(keyNameTail(thisValue)) +
strlen(tcpParamSuffix) + 1];
strcpy(result, srvKeyPrefix);
strcat(result, keyNameTail(thisValue));
strcat(result, tcpParamSuffix);
if (isSubnetExcluded(result)) {
delete[] result;
result = resultSave;
continue;
}
else if (++nTCPKeys > 1) break; // Bail out if more than one!
else {
adapterName = new char[strlen(keyNameTail(thisValue)) + 1];
strcpy(adapterName, keyNameTail(thisValue));
}
}
if (nTCPKeys > 1) fail(mainWindow,
"No unique Registry key for TCP/IP over LAN");
else if (nTCPKeys == 0) fail(mainWindow,
"Can't find Registry key for LAN
TCP/IP");
else openntRarp();
delete bindBuf;
return result;
}
/*
In Windows 2000 we resort to the overkill of issuing the MACADDR ioctl
against the actual device because our Registry navigation still can't
eliminate some ghost devices. Probably the poorly documented Setup
API will get us out of that; Win2K ipconfig appears to use it.
*/
BOOL WorkerParams::probeAdapter(char* anadapterName)
{
char queryResult[512];
char deviceName[80];
BOOL result = FALSE;
BOOL createdDevice = FALSE; // resorted to DefineDosDevice?
BOOL foundDevice = BOOL(QueryDosDevice(anadapterName, queryResult,
sizeof(queryResult)));
if ((!foundDevice) && (GetLastError() == ERROR_FILE_NOT_FOUND)) {
strcpy(deviceName, "\\Device\\");
strcat(deviceName, anadapterName);
createdDevice = DefineDosDevice(DDD_RAW_TARGET_PATH, anadapterName,
deviceName);
}
if (foundDevice || createdDevice) {
char macFileName[80];
strcpy(macFileName, "\\\\.\\");
strcat(macFileName, anadapterName);
HANDLE hMAC = CreateFile(macFileName, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, INVALID_HANDLE_VALUE);
if (hMAC != INVALID_HANDLE_VALUE) {
UCHAR addrData[80];
ULONG ethAddrCode = 0x01010102;
ULONG trAddrCode = 0x02010102;
DWORD returnedCount = DWORD(-1); // impossible value
BOOL ioctlOK = FALSE; // assume failure
DWORD queryIoctl = CTL_CODE(FILE_DEVICE_PHYSICAL_NETCARD, 0,
METHOD_OUT_DIRECT, FILE_ANY_ACCESS);
if (DeviceIoControl(hMAC, queryIoctl /*
IOCTL_NDIS_QUERY_GLOBAL_STATS */,
ðAddrCode, sizeof(ethAddrCode), addrData,
sizeof(addrData), &returnedCount, NULL)) {
ioctlOK = TRUE;
}
else if (DeviceIoControl(hMAC, queryIoctl,
&trAddrCode, sizeof(trAddrCode),
addrData,
sizeof(addrData), &returnedCount, NULL))
{
ioctlOK = TRUE;
}
if (ioctlOK && (returnedCount == 6)) result = TRUE;
else logLine(mainWindow, "... couldn't ioctl device"); //
***temp**
if (createdDevice) { // Get rid of it!
DefineDosDevice(DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION |
DDD_EXACT_MATCH_ON_REMOVE, anadapterName,
deviceName);
}
CloseHandle(hMAC);
}
}
else logLine(mainWindow, "... couldn't query or define device"); //
***temp**
if (result) logLine(mainWindow, "...probed OK"); // ***temp***
return result;
}
/*
Windows NT TCP Registry navigation, NT 5:
We're interested in the network adapters enumerated below the key for
the network adapter class, which in NT 5 involves a GUID. For each
one,
we're only interested if it's bound above to TCP/IP and below to
Ethernet
or Token Ring, and we're only interested if there's one and only one
meeting our criteria. Then computing the keyname is easy after we
open
the corresponding RARP device.
*/
char* WorkerParams::findNT5NetcardTCPParams()
{
char* result = "";
char* netcardClassKeyName =
"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Class\\"
"{4D36E972-E325-11CE-BFC1-08002BE10318}";
char* tcpParamsPrefix =
"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\"
"Parameters\\Interfaces\\";
enum { KeyNameBufLen = 1000, ValueBufLen = 1000 };
DWORD valueBufLen;
char* valueBuf = new char[ValueBufLen];
char* keyNameBuf = new char[KeyNameBufLen];
size_t nTCPKeys = 0; // Want to end up with 1
HKEY netcardClassHandle = getKey(netcardClassKeyName, mainWindow);
if (netcardClassHandle == NULL) {
abortOnBadKey(netcardClassKeyName, mainWindow);
}
for (DWORD adapterNo = 0; ; adapterNo++) {
char netcardSubKeyName[80]; // e.g. "0001"
DWORD netcardSubKeyNameSize = sizeof(netcardSubKeyName);
FILETIME lastUpdate;
LONG rc = RegEnumKeyEx(netcardClassHandle, adapterNo,
netcardSubKeyName,
&netcardSubKeyNameSize, NULL, NULL, NULL,
&lastUpdate);
if (rc != ERROR_SUCCESS) break; // No more subkeys.
strcpy(keyNameBuf, netcardClassKeyName);
strcat(keyNameBuf, "\\");
strcat(keyNameBuf, netcardSubKeyName); // e.g. "0000"
logLine(mainWindow, keyNameBuf); // ***temp***
size_t cardKeyNameLen = strlen(keyNameBuf); // length so far...
valueBufLen = ValueBufLen;
strcpy(keyNameBuf + cardKeyNameLen, "\\Ndi\\Interfaces");
if (!keyExists(keyNameBuf, mainWindow)) continue; // not physical...
// If our driver could handle ALL media we'd use Characteristics of
x84
// as our test of the physicality of the adapter.
if (getValue(keyNameBuf, "LowerRange", (BYTE*)valueBuf, valueBufLen,
REG_SZ, mainWindow) &&
((!stricmp(valueBuf, "ethernet")) ||
(!stricmp(valueBuf, "token ring")))) {
logLine(mainWindow, "...is a physical netcard"); // ***temp***
valueBufLen = ValueBufLen;
keyNameBuf[cardKeyNameLen] = '\0';
if (getValue(keyNameBuf, "NetCfgInstanceId", (BYTE*)valueBuf,
valueBufLen, REG_SZ, mainWindow)) {
logLine(mainWindow, valueBuf); // ***temp***
char* resultSave = result;
result = new char[strlen(tcpParamsPrefix) +
strlen(keyNameTail(valueBuf)) + 1];
strcpy(result, tcpParamsPrefix);
strcat(result, keyNameTail(valueBuf));
#if 0 // ***temp 1.10.1***
HKEY tcpParamsKey = getKey(result, mainWindow);
if (tcpParamsKey != NULL) {
logLine(mainWindow, "...bound to TCP/IP");
RegCloseKey(tcpParamsKey);
}
else {
delete[] result;
result = resultSave;
continue;
}
#endif
valueBufLen = ValueBufLen;
// Is the NTEContextList stuff needed?
char physSignature[ValueBufLen]; // signature of physical
netcard
if (!getValue(result, "NTEContextList", (BYTE*)physSignature,
valueBufLen, REG_MULTI_SZ, mainWindow) ||
isSubnetExcluded(result) ||
!probeAdapter(keyNameTail(valueBuf))) {
delete[] result;
result = resultSave;
continue;
}
else if (++nTCPKeys > 1) break;
else {
adapterName = new char[strlen(keyNameTail(valueBuf)) + 1];
strcpy(adapterName, keyNameTail(valueBuf));
}
}
}
}
RegCloseKey(netcardClassHandle);
if (nTCPKeys > 1) fail(mainWindow,
"No unique Registry key for TCP/IP over LAN");
else if (nTCPKeys == 0) fail(mainWindow,
"Can't find Registry key for LAN
TCP/IP");
else openntRarp();
delete valueBuf;
delete keyNameBuf;
return result;
}
/*
When the Registry fails us we try to get our IP address this way.
*/
UINT getWinsockLocalIp()
{
UINT result = 0;
WSAData wsaData;
if (WSAStartup(MAKEWORD(1, 1), &wsaData) == 0) {
char hostName[200];
if (gethostname(hostName, sizeof(hostName)) == 0) {
struct hostent *phe = gethostbyname(hostName);
if (phe != NULL) {
for (int i = 0; result == 0 && phe->h_addr_list[i] != 0; ++i) {
struct in_addr& ipAddr =
*((struct in_addr*)phe->h_addr_list[i]);
result = ipAddr.s_addr;
}
}
}
WSACleanup();
}
return result;
}
UINT WorkerParams::ourIpAddress()
{
UINT result = 0;
char valueBuf[80];
DWORD valueBufLen1 = sizeof(valueBuf), valueBufLen2 =
sizeof(valueBuf);
if (getValue(transportKeyName, "IPAddress", (BYTE*)valueBuf,
valueBufLen1,
transportValueType, mainWindow)) {
if ((strlen(valueBuf) != 0) && strcmp(valueBuf, "0.0.0.0")) {
result = inet_addr(valueBuf);
}
}
else if (getValue(transportKeyName, "DhcpIPAddress", (BYTE*)valueBuf,
valueBufLen2, transportValueType, mainWindow)) {
if ((strlen(valueBuf) != 0) && strcmp(valueBuf, "0.0.0.0")) {
result = inet_addr(valueBuf);
}
}
return (result ? result : getWinsockLocalIp()); // 10/12/03
}
/*
Here we check if the machine we're running on really uses the
Microsoft
IP stack. We also make sure it isn't set up for DHCP.
*/
BOOL WorkerParams::checkStack()
{
transportKeyName = ((osMajorVersion < 5) ? findNT4NetcardTCPParams() :
findNT5NetcardTCPParams());
return BOOL(transportKeyName[0] != '\0');
}
/*
Diagnostic dump of the driver
*/
void WorkerParams::dumpDriverMemory()
{
if (os == WNT) {
if (ntRarpHandle == INVALID_HANDLE_VALUE) {
fail(mainWindow, "Driver dump requires RARP.SYS");
}
else {
enum { BufSize = 2000 };
BYTE* buf = new BYTE[BufSize];
DWORD bytesReturned;
if (DeviceIoControl(ntRarpHandle, DIOC_RarpDumpDriver,
NULL, 0, buf, BufSize,
&bytesReturned, NULL)) {
dump(mainWindow, bytesReturned, buf, "Driver context:");
}
else logLine(mainWindow, "Can't dump RARP.SYS");
delete buf;
}
}
_endthread();
}
enum { ThreadStackSize = 8192 };
/*
We use _beginthread() and _endthread() rather than the nicer
CreateThread()
etc. because Microsoft says using the latter in a program perverse
enough
to use the C runtime library causes a memory leak. Whether or not
that's
a bug, to ignore the hint would be to punish Microsoft for its
candor. Wouldn't that be the wrong thing to punish them for?
*/
void worker(void* aparams)
{
WorkerParams& params = *((WorkerParams*)aparams);
RarpTbl rarpTbl(params.mainWindow);
if (params.checkStack()) {
if (params.dumpDriver) params.dumpDriverMemory();
ReceiverParams receiverParams(params.mainWindow, params.rxTimeout,
rarpTbl,
params.ourIpAddress(), quiescing);
HANDLE receiverHandle =
HANDLE(_beginthread(rarpReceiver, ThreadStackSize,
&receiverParams));
while (!quiescing) Sleep(1000);
logLine(params.mainWindow, "Quiescing...");
WaitForSingleObject(receiverHandle, INFINITE);
}
SendMessage(params.mainWindow, WM_REALLY_CLOSE, 0, 0);
_endthread();
}
int APIENTRY mainDialogProc(HWND hDlg, UINT message, UINT wParam,
LONG lParam)
{
switch (message)
{
case WM_CLOSE:
quiescing = TRUE;
return 1;
case WM_REALLY_CLOSE:
writeLog(hDlg);
EndDialog (hDlg, 0);
DestroyWindow(hDlg);
PostQuitMessage(0);
return 1;
default:
break;
}
return DefWindowProc (hDlg, message, wParam, lParam);
}
/*
Here we set up the GUI. The use of ShowWindow() is probably inept,
but
it's the only way I could figure out that would allow the nCmdShow
arg
to WinMain() to coerce this program into running minimized if the
user
wants it that way. The problem seems to be that nCmdShow is always
the same (SW_SHOWDEFAULT == decimal 10) no matter what the Win95
Shortcut
Run property is. Am I missing something or what?
Another idisyncrasy here is that we disable the Close item on the
control
menu if we're running under NT with the NT driver. This is because
we
want to be sure any outstanding read gets successfully canceled so
the
driver can be dismissed. This isn't appropriate, though, in case we
want
the user to see the window and dismiss it only after he's seen
enough,
so we make it an option.
*/
BOOL initGUI(HINSTANCE hInstance, HWND& amainWindow, int nCmdShow,
BOOL allowClose)
{
static char appName[] = "RARPD";
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = (WNDPROC)mainDialogProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = DLGWINDOWEXTRA;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, "ICON_3");
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = appName;
RegisterClass(&wndclass);
amainWindow = CreateDialog(hInstance, MAKEINTRESOURCE(DIALOG_MAIN),
NULL,
(DLGPROC)mainDialogProc);
if (amainWindow) {
#if 0
if ((srvHandle != NULL) && !allowClose) {
HMENU ctrlMenuH = GetSystemMenu(amainWindow, FALSE);
if (ctrlMenuH != NULL) {
EnableMenuItem(ctrlMenuH, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
}
}
#endif
ShowWindow(amainWindow,
((nCmdShow == SW_SHOWDEFAULT) ? SW_SHOWMINIMIZED :
nCmdShow));
return TRUE;
}
else {
char* complaint = "Couldn't create main window";
logLastError(0, complaint);
MessageBox(0, complaint, "rarpd", MB_ICONSTOP);
return FALSE;
}
}
/*
Here we extract the /t<seconds> option, returning a millisecond value,
or, failing that, return the default 30,000 milliseconds.
*/
DWORD getTimeout(char* acmdLine)
{
DWORD result = 30000;
if (acmdLine != NULL) {
char* optionP = strstr(acmdLine, "/t");
if (optionP == NULL) optionP = strstr(acmdLine, "/T");
if (optionP != NULL) {
int secs = atoi(optionP + 2);
if (secs > 0) result = secs * 1000;
}
}
return result;
}
/*
We want to set the current directory to the one we were launched from.
This can be tricky because if we were launched by the rarpd Launcher
our full file path is encased in quotes in the command line. Then we
can
open the logfile according to whether the user wants to overwrite any
old
logfile or append to it. If we were launched from the command line
without an explicit path, there's no need to set the current
directory.
*/
void openLog(char* cmdLine)
{
const char* progNameSentinels = (cmdLine[0] == '"') ? "\\\"" : "\\ ";
char* lastBackslash = strchr(cmdLine, '\\');
while (lastBackslash != NULL) {
size_t nextSentinelLoc = strcspn(lastBackslash + 1,
progNameSentinels);
char sentinel = lastBackslash[nextSentinelLoc + 1];
if (sentinel != '\\') break;
lastBackslash += (nextSentinelLoc + 1);
}
char dirPath[MAX_PATH];
if (lastBackslash != NULL) {
char* cmdLineDirStart = cmdLine + strspn(cmdLine, "\"");
size_t dirPathLen = lastBackslash - cmdLineDirStart;
memcpy(dirPath, cmdLineDirStart, dirPathLen);
dirPath[dirPathLen] = '\0';
SetCurrentDirectory(dirPath);
}
if ((strstr(cmdLine, "/a") != NULL) || (strstr(cmdLine, "/A") !=
NULL)) {
outFile.open("RARPD.LOG",ios::out | ios::app);
}
else outFile.open("RARPD.LOG", ios::out | ios::trunc);
char dateStr[80], timeStr[80];
GetDateFormat(LOCALE_USER_DEFAULT, 0, NULL,
"yyyy'-'MM'-'dd", dateStr, sizeof(dateStr));
GetTimeFormat(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT, NULL,
"HH':'mm':'ss", timeStr, sizeof(timeStr));
outFile << "rarpd " VERSION " started " << dateStr << " " << timeStr
<< endl;
outFile << "Command line: " << cmdLine << endl;
}
/*
Here we marshall the options entered from the command line. We also
set the current directory to be the one from which the program was
loaded.
Originally we set the current directory so we could find the VxD even
if
we'd been started from the Registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run.
This reason became obsolete when RARP.vxd became an NDIS protocol
installed so that it gets loaded by the OS from the system directory,
but we left the logic in because it made rarpd.log much easier to
find when a remote user sent email complaining that the program
didn't
work.
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR /* lpCmdLine */, int nCmdShow)
{
ourInstance = hInstance;
HWND mainWindow = 0; // our main window; 0 means not yet
ready
BOOL dumpDriver = FALSE; // dump the driver?
SubnetHolder excludedSubnets;
char* cmdLine = GetCommandLine();
if ((cmdLine != NULL) && (*cmdLine != '\0')) {
openLog(cmdLine);
strupr(cmdLine);
dumpDriver = BOOL(strstr(cmdLine, "/D") != NULL);
excludedSubnets.init(cmdLine);
}
else {
MessageBox(0, "Can't get command line", "rarpd", MB_ICONSTOP);
exit(-1);
}
configureForOS(nCmdShow); // Do this before any screen I/O!
if ((!hPrevInstance) &&
initGUI(hInstance, mainWindow, nCmdShow, dumpDriver)) {
logLine(mainWindow, "RARPD Version " VERSION);
logLine(mainWindow, "Free software copyright (C) 1996-2003 Lew
Perin");
WorkerParams workerParams(mainWindow, dumpDriver,
getTimeout(cmdLine),
excludedSubnets);
_beginthread(worker, ThreadStackSize, &workerParams);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
if (!IsDialogMessage(mainWindow, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
shutdown();
return 0;
} |