windows-nt/Source/XPSP1/NT/admin/dcpromo/exe/checkportavailability.cpp
2020-09-26 16:20:57 +08:00

441 lines
8.9 KiB
C++

// Copyright (C) 2000 Microsoft Corporation
//
// Check availability of ports used by Active Directory
//
// 1 Nov 2000 sburns
#include "headers.hxx"
#include "state.hpp"
#include "resource.h"
#include "CheckPortAvailability.hpp"
static const DWORD HELP_MAP[] =
{
0, 0
};
PortsUnavailableErrorDialog::PortsUnavailableErrorDialog(
StringList& portsInUseList_)
:
Dialog(IDD_PORTS_IN_USE_ERROR, HELP_MAP),
portsInUseList(portsInUseList_)
{
LOG_CTOR(PortsUnavailableErrorDialog);
ASSERT(portsInUseList.size());
}
PortsUnavailableErrorDialog::~PortsUnavailableErrorDialog()
{
LOG_DTOR(PortsUnavailableErrorDialog);
}
void
PortsUnavailableErrorDialog::OnInit()
{
LOG_FUNCTION(PortsUnavailableErrorDialog::OnInit);
// Load up the edit box with the DNs we aliased in the ctor.
String text;
for (
StringList::iterator i = portsInUseList.begin();
i != portsInUseList.end();
++i)
{
text += *i + L"\r\n";
}
Win::SetDlgItemText(hwnd, IDC_PORT_LIST, text);
}
bool
PortsUnavailableErrorDialog::OnCommand(
HWND /* windowFrom */ ,
unsigned controlIDFrom,
unsigned code)
{
// LOG_FUNCTION(PortsUnavailableErrorDialog::OnCommand);
if (code == BN_CLICKED)
{
switch (controlIDFrom)
{
case IDOK:
case IDCANCEL:
{
Win::EndDialog(hwnd, controlIDFrom);
return true;
}
default:
{
// do nothing
}
}
}
return false;
}
// Determine the service name, aliases of that name, and protocol used for a
// given port number. Return S_OK on success, else return a failure code.
//
// winsock must have been initialized prior to calling this function.
//
// portNumber - in, the port number for which the info should be determined.
//
// name - out, the name of the service that runs on that port
//
// aliases - out, other names for the service that runs on the port
//
// protocol - out, the name of the protocol used on the port.
HRESULT
GetServiceOnPort(
int portNumber,
String& name,
StringList& aliases,
String& protocol)
{
LOG_FUNCTION2(GetServiceOnPort, String::format(L"%1!d!", portNumber));
ASSERT(portNumber);
HRESULT hr = S_OK;
name.erase();
aliases.clear();
protocol.erase();
int portNetByteOrder = htons((u_short) portNumber);
servent* se = ::getservbyport(portNetByteOrder, 0);
if (!se)
{
hr = Win32ToHresult((DWORD) ::WSAGetLastError());
}
else
{
if (se->s_name)
{
name = se->s_name;
}
if (se->s_proto)
{
protocol = se->s_proto;
}
char** a = se->s_aliases;
while (*a)
{
aliases.push_back(*a);
++a;
}
}
#ifdef LOGGING_BUILD
LOG_HRESULT(hr);
LOG(name);
for (
StringList::iterator i = aliases.begin();
i != aliases.end();
++i)
{
LOG(*i);
}
LOG(protocol);
#endif
return hr;
}
// S_FALSE if an application has the port open in exclusive mode, S_OK if not,
// and error otherwise.
//
// winsock must have been initialized prior to calling this function.
//
// portNumber - in, port to check.
HRESULT
CheckPortAvailability(int portNumber)
{
LOG_FUNCTION2(CheckPortAvailability, String::format(L"%1!d!", portNumber));
ASSERT(portNumber);
HRESULT hr = S_OK;
do
{
sockaddr_in local;
::ZeroMemory(&local, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons((u_short) portNumber);
local.sin_addr.s_addr = INADDR_ANY;
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
LOG(L"can't build socket");
hr = Win32ToHresult((DWORD) ::WSAGetLastError());
break;
}
if (
bind(
sock,
(sockaddr*) &local,
sizeof local) == SOCKET_ERROR)
{
LOG(L"bind failed");
DWORD sockerr = ::WSAGetLastError();
if (sockerr == WSAEADDRINUSE)
{
// a process on this box already has the socket open in
// exclusive mode.
hr = S_FALSE;
}
else
{
hr = Win32ToHresult(sockerr);
}
break;
}
// at this point, the bind was successful
ASSERT(hr == S_OK);
}
while (0);
LOG_HRESULT(hr);
return hr;
}
// Create a string that represents the port and the service name(s) running on
// that port. This string is presented in the ui.
//
// winsock must have been initialized prior to calling this function.
//
// portNumber - in, port to check.
String
MakeUnavailablePortListEntry(int portNumber)
{
LOG_FUNCTION(MakeUnavailablePortListEntry);
ASSERT(portNumber);
String entry;
do
{
String name;
String protocol;
StringList aliases;
HRESULT hr = GetServiceOnPort(portNumber, name, aliases, protocol);
if (FAILED(hr))
{
// make a simple entry with just the port number
entry = String::format(L"%1!d!", portNumber);
break;
}
if (aliases.size())
{
// combine the aliases into a comma-separated list
String aliasParam;
int j = 0;
for (
StringList::iterator i = aliases.begin();
i != aliases.end();
++i, ++j)
{
aliasParam += *i;
if (j < (aliases.size() - 1))
{
aliasParam += L", ";
}
}
entry =
String::format(
L"%1!d! %2 (%3)",
portNumber,
name.c_str(),
aliasParam.c_str());
}
else
{
// no aliases
entry = String::format(L"%1!d! %2", portNumber, name.c_str());
}
}
while (0);
LOG(entry);
return entry;
}
// Determine if any of a set of tcp ports required by the DS is already in use
// by another application on this machine. Return S_OK if the list can be
// made, a failure code otherwise.
//
// portsInUseList - out, a list of strings representing the ports in use and
// the name(s) of the services that are running on them, suitable for UI
// presentation.
HRESULT
EnumerateRequiredPortsInUse(StringList& portsInUseList)
{
LOG_FUNCTION(EnumerateRequiredPortsInUse);
portsInUseList.clear();
HRESULT hr = S_FALSE;
bool cleanupWinsock = false;
do
{
WSADATA data;
hr = Win32ToHresult((DWORD) ::WSAStartup(MAKEWORD(2,0), &data));
BREAK_ON_FAILED_HRESULT(hr);
cleanupWinsock = true;
static const int REQUIRED_PORTS[] =
{
88, // TCP/UDP Kerberos
389, // TCP LDAP
636, // TCP sldap
3268, // TCP ldap/GC
3269, // TCP sldap/GC
0
};
const int* port = REQUIRED_PORTS;
while (*port)
{
HRESULT hr2 = CheckPortAvailability(*port);
if (hr2 == S_FALSE)
{
// Make an entry in the "in use" list
portsInUseList.push_back(MakeUnavailablePortListEntry(*port));
}
// we ignore any other type of failure and check the remaining
// ports.
++port;
}
}
while (0);
if (cleanupWinsock)
{
::WSACleanup();
}
#ifdef LOGGING_BUILD
LOG_HRESULT(hr);
for (
StringList::iterator i = portsInUseList.begin();
i != portsInUseList.end();
++i)
{
LOG(*i);
}
#endif
return hr;
}
bool
AreRequiredPortsAvailable()
{
LOG_FUNCTION(AreRequiredPortsAvailable);
bool result = true;
do
{
State::RunContext context = State::GetInstance().GetRunContext();
if (context == State::NT5_DC)
{
// already a DC, so we don't care about the port status, as the
// only thing the user will be able to do is demote the box.
LOG(L"already a DC -- port check skipped");
ASSERT(result);
break;
}
// Find the list of IP ports required by the DS that are already in use
// (if any). If we find some, gripe at the user.
StringList portsInUseList;
HRESULT hr = EnumerateRequiredPortsInUse(portsInUseList);
if (FAILED(hr))
{
// if we can't figure out if the required ports are in use, then
// just muddle on -- the user will have to clean up after the
// promote.
ASSERT(result);
break;
}
if (hr == S_FALSE || portsInUseList.size() == 0)
{
LOG(L"No required ports already in use");
ASSERT(result);
break;
}
result = false;
// there should be at least one port in the list.
ASSERT(portsInUseList.size());
PortsUnavailableErrorDialog(portsInUseList).ModalExecute(
Win::GetDesktopWindow());
}
while (0);
LOG(result ? L"true" : L"false");
return result;
}