Click here to Skip to main content
15,881,033 members
Articles / Desktop Programming / MFC

How to write a simple but effective TCP/IP port scanner for Win32

Rate me:
Please Sign up or sign in to vote.
4.82/5 (15 votes)
27 Oct 20017 min read 162.7K   7.3K   101  
An article on how to write a TCP/IP port scanner with a GUI, based on the MFC's property sheet paradigm
/*
	TcpConnectPage.cpp
	Luca Piergentili, 14/07/99
	lpiergentili@yahoo.com
	http://www.geocities.com/lpiergentili/
*/
#include "env.h"
#include "pragma.h"
#include "strcpyn.h"
#include <limits.h>
#include "window.h"
#include "CAsyncSock.h"
#include "CPropertyPageDialog.h"
#include "TcpScanVersion.h"
#include "TcpScanConfig.h"
#include "TcpConnectPage.h"
#include "TcpMessages.h"
#include "resource.h"

//static void AFXAPI DDV_ValidateIP(CDataExchange*,int,CString&);
//static void AFXAPI DDV_ValidatePortNumber(CDataExchange*,int,unsigned int&);

IMPLEMENT_DYNCREATE(CConnectPage,CPropertyPageDialog)

BEGIN_MESSAGE_MAP(CConnectPage,CPropertyPageDialog)
	ON_CBN_KILLFOCUS(IDC_COMBO_HOST,OnKillFocusComboHost)
	ON_CBN_KILLFOCUS(IDC_COMBO_PORT,OnKillFocusComboPort)
	ON_MESSAGE(WM_ASYNCCONNECT,OnConnect)
	ON_MESSAGE(WM_ASYNCREAD,OnRead)
	ON_MESSAGE(WM_ASYNCWRITE,OnWrite)
	ON_MESSAGE(WM_ASYNCCLOSE,OnClose)
END_MESSAGE_MAP()

/*
	DoDataExchange()
*/
void CConnectPage::DoDataExchange(CDataExchange* pDX)
{
	CPropertyPage::DoDataExchange(pDX);

	DDX_Control(pDX,IDC_COMBO_HOST,m_wndComboHost);
	DDX_Control(pDX,IDC_COMBO_PORT,m_wndComboPort);
	DDX_Text(pDX,IDC_EDIT_SEND,m_strHostSend);
	DDX_Text(pDX,IDC_EDIT_RECV,m_strHostRecv);

	// effettua i controlli solo se deve effettivamente validare i dati
	// (click su IDOK/IDCANCEL e non il semplice spostamento da una pagina all'altra)
	if(pDX->m_bSaveAndValidate && !KillPage())
	{
		;
	}
}

/*
	CConnectPage()
*/
CConnectPage::CConnectPage() : CPropertyPageDialog(IDD_PAGE_CONNECT)
{
	m_pConnectButtonText = "&Connect";
	m_pSendButtonText    = "&Send";
	m_pCancelButtonText  = "&Cancel";
	SetPropertyPageOkButton(m_pConnectButtonText);

	m_nMaxHostAddr       = 0;
	m_nMaxPortNumber     = 0;
	m_pConfig            = NULL;
	m_pAsyncSocket       = new CAsyncSock(this);

	m_strConnectHostAddr = DEFAULT_HOST;
	m_nConnectHostPort   = 0;
	m_strHostSend        = "";
	m_strHostRecv        = "";
}

/*
	~CConnectPage()
*/
CConnectPage::~CConnectPage()
{
	if(m_pAsyncSocket)
		delete m_pAsyncSocket,m_pAsyncSocket = NULL;
}

/*
	OnInitDialog()

	Inizializzazione del dialogo.
	Rimappa la funzione base per impostare l'host e la porta (in base al meccanismo di controllo 
	effettuato tramite UpdateData(), DDX, DDV e OnKillPage() deve impostare i valori a mano).
*/
BOOL CConnectPage::OnInitDialog(void)
{
	// deve chiamare il gestore originale
	CPropertyPageDialog::OnInitDialog();

	// numero max di host per il combo
	m_nMaxHostAddr = m_pConfig->GetNumber(TCPSCAN_OPTIONS_KEY,TCPSCAN_MAX_HOSTENTRY_KEY);

	// carica il nome dell'host presente nella configurazione (se non e' stato specificato nessun nome imposta su quello di default)
	char* pHost = (char*)m_pConfig->GetString(TCPSCAN_CONNECT_KEY,TCPSCAN_HOST_KEY);
	if(strcmp(pHost,"")==0)
		pHost = LOCAL_HOST;

	m_strConnectHostAddr.Format("%s",pHost);

	// imposta il testo del combo per l'host, aggiungendolo come primo elemento
	m_wndComboHost.SetWindowText(m_strConnectHostAddr);
	UpdateData(FALSE);
	m_wndComboHost.AddString(m_strConnectHostAddr);

	// inserisce nel combo gli eventuali host presenti
	char* p;
	char szKey[REGKEY_MAX_KEY_NAME+1];
	for(int i = 0; i < m_nMaxHostAddr; i++)
	{
		_snprintf(szKey,sizeof(szKey)-1,"%s%d",TCPSCAN_HOSTADDR_KEY,i);
		if(strcmp((p = (char*)m_pConfig->GetString(TCPSCAN_CONNECT_KEY,szKey)),"")!=0)
		{
			if(m_wndComboHost.FindStringExact(-1,p)==CB_ERR)
				m_wndComboHost.AddString(p);
		}
		else
			break;
	}
	m_wndComboHost.SetCurSel(0);

	// numero max di porte per il combo
	m_nMaxPortNumber = m_pConfig->GetNumber(TCPSCAN_OPTIONS_KEY,TCPSCAN_MAX_PORTENTRY_KEY);

	// carica il numero della porta presente nella configurazione (se non e' stato specificato nessun nome imposta su quello di default)
	UINT nPortNumber = m_pConfig->GetNumber(TCPSCAN_CONNECT_KEY,TCPSCAN_PORT_KEY);
	if(nPortNumber==(UINT)-1)
		nPortNumber = 0;
	m_nConnectHostPort = nPortNumber;

	// imposta il testo del combo per la porta, aggiungendola come primo elemento
	char szPort[10];
	_snprintf(szPort,sizeof(szPort)-1,"%u",m_nConnectHostPort);
	m_wndComboPort.SetWindowText(szPort);
	UpdateData(FALSE);
	m_wndComboPort.AddString(szPort);

	// inserisce nel combo le eventuali porte presenti
	for(i = 0; i < m_nMaxPortNumber; i++)
	{
		_snprintf(szKey,sizeof(szKey)-1,"%s%d",TCPSCAN_PORTNUMBER_KEY,i);
		if((nPortNumber = m_pConfig->GetNumber(TCPSCAN_CONNECT_KEY,szKey))!=(UINT)-1)
		{
			_snprintf(szPort,sizeof(szPort)-1,"%u",nPortNumber);
			if(m_wndComboPort.FindStringExact(-1,szPort)==CB_ERR)
				m_wndComboPort.AddString(szPort);
		}
		else
			break;
	}
	m_wndComboPort.SetCurSel(0);

	// imposta il default per il campo numerico (per i campi stringa non serve)
	// (non puo' chiamare UpdateData() ma deve impostare a mano, vedi sopra)
	CWnd* pCWnd;
	if((pCWnd = GetDlgItem(IDC_EDIT_HOSTPORT))!=NULL)
		pCWnd->SetWindowText("0");
	
	return(FALSE);
}

/*
	OnSetActive()

	Attivazione della pagina.
	Rimappa la funzione base per cambiare il testo del bottone IDOK dello sheet.
*/
BOOL CConnectPage::OnSetActive(void)
{
	// deve chiamare il gestore originale
	CPropertyPageDialog::OnSetActive();
	
	// modifica il testo del bottone IDOK dello sheet
	SetPropertySheetOkButton(GetPropertyPageOkButton());

	if(LookForBusyPages(IDD_PAGE_CONNECT))
		DisablePropertySheetOkButton();

	if(LookForPageBusy(IDD_PAGE_CONNECT))
		EnablePropertySheetOkButton();

	return(TRUE);
}

/*
	OnKillActive()

	Disattivazione della pagina.
*/
BOOL CConnectPage::OnKillActive(void)
{
	// deve chiamare il gestore originale
	CPropertyPageDialog::OnKillActive();

	return(TRUE);
}

/*
	OnKillSheet()

	Chiusura del dialogo.
*/
void CConnectPage::OnKillSheet(void)
{
	::SendMessage(this->m_hWnd,WM_ASYNCCONNECT,WM_CLOSE,WM_CLOSE);

	if(IsWindow(this->m_hWnd))
	{
		// la configurazione viene salvata nelle OnKillFocus...()
		;
	}
}

/*
	OnOk()
*/
void CConnectPage::OnOk(void)
{
	EnterPageBusyState(IDD_PAGE_CONNECT);

	int nWSAError = 0;
	static char szWSAString[128];
	strcpy(szWSAString,"0");
	::PostMessage(this->m_hWnd,WM_ASYNCCONNECT,nWSAError,MAKELPARAM(LOWORD(szWSAString),HIWORD(szWSAString)));
}

/*
	OnCancel()
*/
void CConnectPage::OnCancel(void)
{
	::PostMessage(this->m_hWnd,WM_ASYNCCONNECT,WM_CANCEL,WM_CANCEL);

	LeavePageBusyState(IDD_PAGE_CONNECT);
}

/*
	OnKillFocusComboHost()

	Aggiorna il valore utilizzato come host per la connessione e la configurazione.
*/
void CConnectPage::OnKillFocusComboHost(void)
{
	// ricava il testo del combo aggiornando di conseguenza il nome dell'host e la configurazione
	char szComboEntry[HOSTNAME_SIZE+1];
	m_wndComboHost.GetWindowText(szComboEntry,sizeof(szComboEntry));

	// controlla se e' stato cambiato il testo del combo
	if(strcmp(szComboEntry,LPCSTR(m_strConnectHostAddr))!=0)
	{
		// aggiorna il nome dell'host di default
		m_strConnectHostAddr.Format("%s",szComboEntry);

		// aggiorna il nome dell'host di default nella configurazione
		m_pConfig->UpdateString(TCPSCAN_CONNECT_KEY,TCPSCAN_HOST_KEY,szComboEntry);

		// cerca il testo del combo nella configurazione, se non lo trova deve inserirlo nel
		// nel combo e nella configurazione
		char* p;
		char szKey[REGKEY_MAX_KEY_NAME+1];
		BOOL bFound = FALSE;

		for(int i = 0; i < m_nMaxHostAddr; i++)
		{
			_snprintf(szKey,sizeof(szKey)-1,"%s%d",TCPSCAN_HOSTADDR_KEY,i);
			if(strcmp((p = (char*)m_pConfig->GetString(TCPSCAN_CONNECT_KEY,szKey)),"")!=0)
			{
				if(strcmp(szComboEntry,p)==0)
				{
					bFound = TRUE;
					break;
				}
			}
			else
				break;
		}

		// non trovato, lo inserisce nel combo se gia' non esiste
		if(!bFound)
		{
			if(i==m_nMaxHostAddr)
				m_wndComboHost.DeleteString(0);

			if(m_wndComboHost.FindStringExact(-1,szComboEntry)==CB_ERR)
				m_wndComboHost.AddString(szComboEntry);
		}

		// aggiorna le entrate della configurazione con quanto presente nel combo (elimina e reinserisce)
		for(i = 0; i < m_nMaxHostAddr; i++)
		{
			_snprintf(szKey,sizeof(szKey)-1,"%s%d",TCPSCAN_HOSTADDR_KEY,i);
			if(strcmp(m_pConfig->GetString(TCPSCAN_CONNECT_KEY,szKey),"")!=0)
				m_pConfig->Delete(TCPSCAN_CONNECT_KEY,szKey);
		}
		m_pConfig->Save();

		for(i = 0; i < m_nMaxHostAddr; i++)
		{
			if(m_wndComboHost.GetLBText(i,szComboEntry)!=CB_ERR)
			{
				_snprintf(szKey,sizeof(szKey)-1,"%s%d",TCPSCAN_HOSTADDR_KEY,i);
				m_pConfig->Insert(TCPSCAN_CONNECT_KEY,szKey,szComboEntry);
			}
		}
		m_pConfig->Save();
	}
}

/*
	OnKillFocusComboPort()

	Aggiorna il valore utilizzato come port per la connessione e la configurazione.
*/
void CConnectPage::OnKillFocusComboPort(void)
{
	// ricava il testo del combo aggiornando di conseguenza la porta e la configurazione
	char szComboEntry[HOSTNAME_SIZE+1];
	m_wndComboPort.GetWindowText(szComboEntry,sizeof(szComboEntry));

	if(strcmp(szComboEntry,"")==0 || atoi(szComboEntry) < 0)
	{
		m_wndComboPort.SetWindowText("0");
		m_nConnectHostPort = 0;
		UpdateData(FALSE);
		return;
	}

	char szPort[16];
	_snprintf(szPort,sizeof(szPort)-1,"%u",m_nConnectHostPort);

	// controlla se e' stato cambiato il testo del combo
	if(strcmp(szComboEntry,szPort)!=0)
	{
		// aggiorna il nome dell'host di default
		m_nConnectHostPort = (UINT)atol(szComboEntry);

		// aggiorna la porta di default nella configurazione
		m_pConfig->UpdateNumber(TCPSCAN_CONNECT_KEY,TCPSCAN_PORT_KEY,m_nConnectHostPort);

		// cerca il testo del combo nella configurazione, se non lo trova deve inserirlo nel
		// nel combo e nella configurazione
		UINT nPortNumber;
		char szKey[REGKEY_MAX_KEY_NAME+1];
		BOOL bFound = FALSE;

		for(int i = 0; i < m_nMaxPortNumber; i++)
		{
			_snprintf(szKey,sizeof(szKey)-1,"%s%d",TCPSCAN_PORTNUMBER_KEY,i);
			if((nPortNumber = m_pConfig->GetNumber(TCPSCAN_CONNECT_KEY,szKey))!=(UINT)-1)
			{
				_snprintf(szPort,sizeof(szPort)-1,"%u",nPortNumber);
				if(strcmp(szComboEntry,szPort)==0)
				{
					bFound = TRUE;
					break;
				}
			}
			else
				break;
		}

		// non trovato, lo inserisce nel combo se gia' non esiste
		if(!bFound)
		{
			if(i==m_nMaxPortNumber)
				m_wndComboPort.DeleteString(0);

			if(m_wndComboPort.FindStringExact(-1,szComboEntry)==CB_ERR)
				m_wndComboPort.AddString(szComboEntry);
		}

		// aggiorna le entrate della configurazione con quanto presente nel combo (elimina e reinserisce)
		for(i = 0; i < m_nMaxPortNumber; i++)
		{
			_snprintf(szKey,sizeof(szKey)-1,"%s%d",TCPSCAN_PORTNUMBER_KEY,i);
			if(m_pConfig->GetNumber(TCPSCAN_CONNECT_KEY,szKey)!=(UINT)-1)
				m_pConfig->Delete(TCPSCAN_CONNECT_KEY,szKey);
		}
		m_pConfig->Save();

		for(i = 0; i < m_nMaxPortNumber; i++)
		{
			if(m_wndComboPort.GetLBText(i,szComboEntry)!=CB_ERR)
			{
				_snprintf(szKey,sizeof(szKey)-1,"%s%d",TCPSCAN_PORTNUMBER_KEY,i);
				nPortNumber = atol(szComboEntry);
				m_pConfig->Insert(TCPSCAN_CONNECT_KEY,szKey,nPortNumber);
			}
		}
		m_pConfig->Save();
	}
}

/*
	OnConnect()

	Effettua il collegamento all'host/porta specificati.
	Chiamata dallo sheet per il click sul bottone (IDOK).
*/
LONG CConnectPage::OnConnect(UINT wParam,LONG lParam)
{
	static int nConnectIndex = -1;
	static TCPSCAN tcpscan;
	char* p;
	char* pIpAddress = {LOCAL_HOST};
	CString strBuffer;

	// e' stato cliccato il bottone Cancel, annulla la connessione
	if(CHECK_PARAM_MESSAGE(WM_CANCEL))
	{
		if(m_pAsyncSocket)
			m_pAsyncSocket->Close();
		wParam = 1;
		lParam = 0;
	}
	// e' stato cliccato il bottone Exit, annulla la connessione
	else if(CHECK_PARAM_MESSAGE(WM_CLOSE))
	{
		if(m_pAsyncSocket)
			m_pAsyncSocket->Close();
		wParam = lParam = 0;
		return(0);
	}

	// controlla che il costruttore sia riuscito nella inizializzazione
	if(!m_pAsyncSocket)
	{
		AfxMessageBox("Unable to create the internal socket.",MB_ICONWARNING);
		return(0);
	}

	// per gestire i due tipi di connessione (aperta, come con FTP, o chiusa, come con HTTP)
	// ricava il testo del bottone IDOK per sapere se si tratta della prima chiamata o delle successive
	char szText[32];
	GetPropertySheetOkButton(szText);
	if(strcmp(szText,"&Send")==0)
	{
		nConnectIndex = 1;
	}
	else
	{
		if(nConnectIndex!=0)
			nConnectIndex = -1;
	}

	// prima chiamata, effettua la connessione
	if(nConnectIndex < 0)
	{
		// disabilita il bottone Connect e trasforma Exit in Cancel
		DisablePropertySheetOkButton();
		SetPropertySheetCancelButton(IDCANCEL_CANCEL_PROMPT);

		nConnectIndex = 0;

		while((p = (char*)m_pAsyncSocket->ParseIPRange(m_strConnectHostAddr,m_strConnectHostAddr))!=NULL)
			pIpAddress = p;

		m_strConnectHostAddr = pIpAddress;
		UpdateData(FALSE);

		strcpyn(tcpscan.ip,m_strConnectHostAddr,IP_ADDRESS_SIZE+1);
		tcpscan.port = m_nConnectHostPort;

		TCPSCAN* t = &tcpscan;

		// effettua il collegamento all'host/porta specificati
		if(m_pAsyncSocket->Open())
		{
			strBuffer.Format("connecting to %s:%d...",tcpscan.ip,tcpscan.port);
			SetTitle(strBuffer);
			//m_pAsyncSocket->SetSocketTimeout(10);
			m_pAsyncSocket->AsyncConnect(t->ip,t->port);
		}
		else
		{
			// apertura fallita, invia autonomamente il messaggio
			int nWSAError = m_pAsyncSocket->GetWSAErrorNumber();
			static char szWSAString[128];
			strcpyn(szWSAString,m_pAsyncSocket->GetWSAErrorString(),sizeof(szWSAString));
			::PostMessage(this->m_hWnd,WM_ASYNCCONNECT,nWSAError,MAKELPARAM(LOWORD(szWSAString),HIWORD(szWSAString)));
			strBuffer.Format("error: %s",szWSAString);
			SetTitle(strBuffer);
		}
	}
	else // chiamate successive
	{
		BOOL bConnected = (BOOL)((int)wParam==0);

		if(!bConnected)
		{
			if(m_pAsyncSocket->IsWSAError(wParam))
			{
				strBuffer.Format("%s:%d error: %s",tcpscan.ip,tcpscan.port,m_pAsyncSocket->GetWSAErrorString());
				SetTitle(strBuffer);
				AfxMessageBox(strBuffer,MB_ICONWARNING);
			}

			m_pAsyncSocket->Close();
			strBuffer.Format("%s:%d: connection closed",tcpscan.ip,tcpscan.port);
			SetTitle(strBuffer);
			AfxMessageBox(strBuffer,MB_ICONINFORMATION);

			// abilita il bottone Connect e trasforma Cancel in Exit
			SetPropertySheetOkButton(m_pConnectButtonText);
			EnablePropertySheetOkButton();
			SetPropertySheetCancelButton(IDCANCEL_EXIT_PROMPT);
			LeavePageBusyState(IDD_PAGE_CONNECT);
			
			nConnectIndex = -1;
		}
		else
		{
			if(nConnectIndex==0)
			{
				// abilita il bottone Send e trasforma Exit in Cancel
				SetPropertySheetOkButton(m_pSendButtonText);
				EnablePropertySheetOkButton();
				SetPropertySheetCancelButton(IDCANCEL_CANCEL_PROMPT);
				
				nConnectIndex = -1;
			}
			else
			{
				UpdateData(TRUE);

				if(m_strHostSend.GetLength() > 0)
				{
					m_pAsyncSocket->SetData(m_strHostSend);
					if(m_pAsyncSocket->Send() < 0)
						::PostMessage(this->m_hWnd,WM_ASYNCCLOSE,0,0);
				}
			}
		}

		SetTitle(PROGRAM_NAME);
	}
	
	return(0L);
}

/*
	OnRead()
	
	Chiamata dal socket per la ricezione dei dati.
*/
LONG CConnectPage::OnRead(UINT /*wParam*/,LONG /*lParam*/)
{
	while(m_pAsyncSocket->Receive() > 0)
		m_strHostRecv += m_pAsyncSocket->GetData();

	// aggiorna i campi della pagina
	UpdateData(FALSE);

	// per posizionarsi alla fine
	CEdit* pCEdit = (CEdit*)GetDlgItem(IDC_EDIT_RECV);
	if(pCEdit)
		pCEdit->LineScroll(pCEdit->GetLineCount());

	return(0L);
}

/*
	OnWrite()
	
	Chiamata dal socket per l'invio dei dati.
*/
LONG CConnectPage::OnWrite(UINT /*wParam*/,LONG /*lParam*/)
{
	if(m_strHostSend.GetLength() > 0)
	{
		m_pAsyncSocket->SetData(m_strHostSend);
		m_pAsyncSocket->Send();
	}

	return(0L);
}

/*
	OnClose()
	
	Chiamata dal socket per la chiusura.
*/
LONG CConnectPage::OnClose(UINT /*wParam*/,LONG /*lParam*/)
{
	m_pAsyncSocket->Close();

	// aggiorna il testo dei bottoni dello sheet
	SetPropertySheetOkButton(m_pConnectButtonText);
	EnablePropertySheetOkButton();
	SetPropertySheetCancelButton(IDCANCEL_EXIT_PROMPT);

	AfxMessageBox("Connection closed by the remote host.",MB_ICONINFORMATION);

	LeavePageBusyState(IDD_PAGE_CONNECT);

	return(0L);
}

/*
	DDV_...()
*/
/*
void AFXAPI DDV_ValidateIP(CDataExchange* pDX,int nIDC,CString& cIpAddr)
{
	pDX->PrepareEditCtrl(nIDC);
	
	if(pDX->m_bSaveAndValidate)
		if(cIpAddr.GetLength() <= 0 || cIpAddr.GetLength() > HOSTNAME_SIZE)
		{
			AfxMessageBox("Must enter a valid ip string.",MB_ICONWARNING);
			pDX->Fail();
		}
}

void AFXAPI DDV_ValidatePortNumber(CDataExchange* pDX,int nIDC,unsigned int& iPort)
{
	pDX->PrepareEditCtrl(nIDC);
	
	if(pDX->m_bSaveAndValidate)
		if(iPort < 0 || iPort >= UINT_MAX)
		{
			AfxMessageBox("Must enter a valid port number.",MB_ICONWARNING);
			pDX->Fail();
		}
}
*/

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Italy Italy
I like C and C++, Acid Jazz, James Brown, gli Spaghetti Aglio e Olio, alla Bolognesa, alla Puttanesca e le Fettuccine alla Matriciana ('Maccaroni' over the world). Of course I like beautiful big tits girls too, my little car, Frank Zappa, the art of Zen, italian coffee and much more...

Comments and Discussions