//this file is part of eMule //Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net ) // //This program 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 of the License, or (at your option) any later version. // //This program 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 program; if not, write to the Free Software //Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include "stdafx.h" #include "emule.h" #include "IPFilter.h" #include "otherfunctions.h" #include "Preferences.h" #include "emuledlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define DFLT_FILTER_LEVEL 100 // default filter level if non specified CIPFilter::CIPFilter() { m_pLastHit = NULL; LoadFromDefaultFile(false); } CIPFilter::~CIPFilter() { RemoveAllIPs(); } void CIPFilter::AddIPRange(uint32 start, uint32 end, UINT level, const CString& desc) { SIPFilter* newFilter = new SIPFilter; newFilter->start = start; newFilter->end = end; newFilter->level = level; newFilter->desc = desc; newFilter->hits = 0; m_iplist.Add(newFilter); } static int __cdecl CmpSIPFilterByStartAddr(const void* p1, const void* p2) { const SIPFilter* rng1 = *(SIPFilter**)p1; const SIPFilter* rng2 = *(SIPFilter**)p2; return CompareUnsigned(rng1->start, rng2->start); } int CIPFilter::LoadFromDefaultFile(bool bShowResponse) { RemoveAllIPs(); return AddFromFile(thePrefs.GetConfigDir() + DFLT_IPFILTER_FILENAME, bShowResponse); } int CIPFilter::AddFromFile(LPCTSTR pszFilePath, bool bShowResponse) { FILE* readFile = fopen(pszFilePath, "r"); if (readFile != NULL) { enum EIPFilterFileType { Unknown = 0, FilterDat = 1, // ipfilter.dat/ip.prefix format PeerGuardian = 2 // PeerGuardian format } eFileType = Unknown; TCHAR szNam[_MAX_FNAME]; TCHAR szExt[_MAX_EXT]; _tsplitpath(pszFilePath, NULL, NULL, szNam, szExt); if (_tcsicmp(szExt, _T(".p2p")) == 0 || (_tcsicmp(szNam, _T("guarding.p2p")) == 0 && _tcsicmp(szExt, _T(".txt")) == 0)) eFileType = PeerGuardian; else if (_tcsicmp(szExt, _T(".prefix")) == 0) eFileType = FilterDat; int iLine = 0; int iFoundRanges = 0; int iDuplicate = 0; int iMerged = 0; CString sbuffer; char szBuffer[1024]; while (fgets(szBuffer, ARRSIZE(szBuffer), readFile) != NULL) { iLine++; sbuffer = szBuffer; // ignore comments & too short lines if (sbuffer.GetAt(0) == _T('#') || sbuffer.GetAt(0) == _T('/') || sbuffer.GetLength() < 5) continue; if (eFileType == Unknown) { // looks like html if (sbuffer.Find(_T('>')) > -1 && sbuffer.Find(_T('<')) > -1) sbuffer.Delete(0, sbuffer.ReverseFind(_T('>')) + 1); // check for - at start of line UINT u1, u2, u3, u4, u5, u6, u7, u8; if (_stscanf(sbuffer, _T("%u.%u.%u.%u - %u.%u.%u.%u"), &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) == 8) { eFileType = FilterDat; } else { // check for ':' '-' int iColon = sbuffer.Find(_T(':')); if (iColon > -1) { CString strIPRange = sbuffer.Mid(iColon + 1); UINT u1, u2, u3, u4, u5, u6, u7, u8; if (_stscanf(strIPRange, _T("%u.%u.%u.%u - %u.%u.%u.%u"), &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) == 8) { eFileType = PeerGuardian; } } } } bool bValid = false; uint32 start, end; UINT level; CString desc; if (eFileType == FilterDat) bValid = ParseFilterLine1(sbuffer, start, end, level, desc); else if (eFileType == PeerGuardian) bValid = ParseFilterLine2(sbuffer, start, end, level, desc); // add a filter if (bValid) { AddIPRange(start, end, level, desc); iFoundRanges++; } } fclose(readFile); // sort the IP filter list by IP range start addresses qsort(m_iplist.GetData(), m_iplist.GetCount(), sizeof(m_iplist[0]), CmpSIPFilterByStartAddr); // merge overlapping and adjacent filter ranges if (m_iplist.GetCount() >= 2) { SIPFilter* pPrv = m_iplist[0]; int i = 1; while (i < m_iplist.GetCount()) { SIPFilter* pCur = m_iplist[i]; if ( pCur->start >= pPrv->start && pCur->start <= pPrv->end // overlapping || pCur->start == pPrv->end+1 && pCur->level == pPrv->level) // adjacent { if (pCur->start != pPrv->start || pCur->end != pPrv->end) // don't merge identical entries { //TODO: not yet handled, overlapping entries with different 'level' if (pCur->end > pPrv->end) pPrv->end = pCur->end; //pPrv->desc += _T("; ") + pCur->desc; // this may create a very very long description string... iMerged++; } else { // if we have identical entries, use the lowest 'level' if (pCur->level < pPrv->level) pPrv->level = pCur->level; iDuplicate++; } delete pCur; m_iplist.RemoveAt(i); continue; } pPrv = pCur; i++; } } theApp.emuledlg->AddLogLine(bShowResponse, GetResString(IDS_IPFILTERLOADED), m_iplist.GetCount()); if (thePrefs.GetVerbose()) { theApp.emuledlg->AddDebugLogLine(false, _T("Loaded IP filters from \"%s\""), pszFilePath); theApp.emuledlg->AddDebugLogLine(false, _T("Parsed lines:%u Found IP ranges:%u Duplicate:%u Merged:%u"), iLine, iFoundRanges, iDuplicate, iMerged); } } return m_iplist.GetCount(); } void CIPFilter::SaveToDefaultFile() { CString strFilePath = thePrefs.GetConfigDir() + DFLT_IPFILTER_FILENAME; FILE* fp = fopen(strFilePath, "wt"); if (fp != NULL) { for (int i = 0; i < m_iplist.GetCount(); i++) { const SIPFilter* flt = m_iplist[i]; char szStart[16]; in_addr ip; ip.S_un.S_addr = htonl(flt->start); strcpy(szStart, inet_ntoa(ip)); char szEnd[16]; ip.S_un.S_addr = htonl(flt->end); strcpy(szEnd, inet_ntoa(ip)); if (fprintf(fp, "%-15s - %-15s , %3u , %s\n", szStart, szEnd, flt->level, flt->desc) == 0 || ferror(fp)) { CString strError; strError.Format(_T("Failed to save IP filter to file \"%s\" - %hs"), strFilePath, strerror(errno)); throw strError; } } fclose(fp); } else { CString strError; strError.Format(_T("Failed to save IP filter to file \"%s\" - %hs"), strFilePath, strerror(errno)); throw strError; } } bool CIPFilter::ParseFilterLine1(const CString& sbuffer, uint32& ip1, uint32& ip2, UINT& level, CString& desc) const { TCHAR szDesc[256]; memset(szDesc, 0, sizeof szDesc); UINT u1, u2, u3, u4, u5, u6, u7, u8, uLevel = DFLT_FILTER_LEVEL; int iItems = _stscanf(sbuffer, _T("%u.%u.%u.%u - %u.%u.%u.%u , %u , %255c"), &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8, &uLevel, szDesc); if (iItems < 8) return false; ((BYTE*)&ip1)[0] = u4; ((BYTE*)&ip1)[1] = u3; ((BYTE*)&ip1)[2] = u2; ((BYTE*)&ip1)[3] = u1; ((BYTE*)&ip2)[0] = u8; ((BYTE*)&ip2)[1] = u7; ((BYTE*)&ip2)[2] = u6; ((BYTE*)&ip2)[3] = u5; if (iItems == 8) { level = DFLT_FILTER_LEVEL; // set default level return true; } level = uLevel; if (iItems == 9) return true; // no description available ASSERT( iItems == 10 ); desc = szDesc; desc.Trim(); return true; } bool CIPFilter::ParseFilterLine2(const CString& sbuffer, uint32& ip1, uint32& ip2, UINT& level, CString& desc) const { int iPos = sbuffer.Find(_T(':')); if (iPos < 1) return false; desc = sbuffer.Left(iPos); desc.Replace(_T("PGIPDB"), _T("")); desc.Trim(); CString strIPRange = sbuffer.Mid(iPos + 1, sbuffer.GetLength() - iPos); UINT u1, u2, u3, u4, u5, u6, u7, u8; if (_stscanf(strIPRange, _T("%u.%u.%u.%u - %u.%u.%u.%u"), &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) != 8) return false; ((BYTE*)&ip1)[0] = u4; ((BYTE*)&ip1)[1] = u3; ((BYTE*)&ip1)[2] = u2; ((BYTE*)&ip1)[3] = u1; ((BYTE*)&ip2)[0] = u8; ((BYTE*)&ip2)[1] = u7; ((BYTE*)&ip2)[2] = u6; ((BYTE*)&ip2)[3] = u5; level = DFLT_FILTER_LEVEL; return true; } void CIPFilter::RemoveAllIPs() { for (int i = 0; i < m_iplist.GetCount(); i++) delete m_iplist[i]; m_iplist.RemoveAll(); m_pLastHit = NULL; } bool CIPFilter::IsFiltered(uint32 ip) /*const*/ { return IsFiltered(ip, thePrefs.GetIPFilterLevel()); } static int __cdecl CmpSIPFilterByAddr(const void* pvKey, const void* pvElement) { uint32 ip = *(uint32*)pvKey; const SIPFilter* pIPFilter = *(SIPFilter**)pvElement; if (ip < pIPFilter->start) return -1; if (ip > pIPFilter->end) return 1; return 0; } bool CIPFilter::IsFiltered(uint32 ip, UINT level) /*const*/ { if (m_iplist.GetCount() == 0 || ip == 0) return false; ip = htonl(ip); /*for (int i = 0; i < m_iplist.GetCount(); i++) { SIPFilter* search = m_iplist[i]; if (ip < search->start) return false; if (search->level < level && ip >= search->start && ip <= search->end) { search->hits++; m_pLastHit = search; return true; } }*/ // to speed things up we use a binary search // *) the IP filter list must be sorted by IP range start addresses // *) the IP filter list is not allowed to contain overlapping IP ranges (see also the IP range merging code when // loading the list) // *) the filter 'level' is ignored during the binary search and is evaluated only for the found element // // TODO: this can still be improved even more: // *) use a pre assembled list of IP ranges which contains only the IP ranges for the currently used filter level // *) use a dumb plain array for storing the IP range structures. this will give more cach hits when processing // the list. but(!) this would require to also use a dumb SIPFilter structure (don't use data items with ctors). // otherwise the creation of the array would be rather slow. SIPFilter** ppFound = (SIPFilter**)bsearch(&ip, m_iplist.GetData(), m_iplist.GetCount(), sizeof(m_iplist[0]), CmpSIPFilterByAddr); if (ppFound && (*ppFound)->level < level) { (*ppFound)->hits++; m_pLastHit = *ppFound; return true; } return false; } LPCTSTR CIPFilter::GetLastHit() const { return m_pLastHit ? m_pLastHit->desc : _T("Not available"); } const CIPFilterArray& CIPFilter::GetIPFilter() const { return m_iplist; } bool CIPFilter::RemoveIPFilter(const SIPFilter* pFilter) { for (int i = 0; i < m_iplist.GetCount(); i++) { if (m_iplist[i] == pFilter) { delete m_iplist[i]; m_iplist.RemoveAt(i); return true; } } return false; }