/*
 * ipauth.cxx
 *
 * IP based authentication modules
 *
 * @(#) $Id: ipauth.cxx,v 1.3 2006/06/12 12:32:44 zvision Exp $
 *
 * Copyright (c) 2005, Michal Zygmuntowicz
 *
 * This work is published under the GNU Public License (GPL)
 * see file COPYING for details.
 * We also explicitely grant the right to link this code
 * with the OpenH323 library.
 */
#include <ptlib.h>
#include <h225.h>

#include "gk_const.h"
#include "h323util.h"
#include "stl_supp.h"
#include "Toolkit.h"
#include "RasPDU.h"
#include "RasTbl.h"
#include "sigmsg.h"
#include "ipauth.h"

class IPAuthPrefix {
public:
	IPAuthPrefix();
	IPAuthPrefix(bool a, const PString &);
	IPAuthPrefix(const IPAuthPrefix &);

	void AddPrefix(const PString &);
	void SortPrefix(bool greater = true);
	int PrefixMatch(const PString &) const;
	std::string PrintOn(void) const;
	std::string PrintPrefix(void) const;

	IPAuthPrefix& operator=(const IPAuthPrefix&);
	IPAuthPrefix& operator=(bool);

        typedef std::vector<std::string>::iterator prefix_iterator;
        typedef std::vector<std::string>::const_iterator const_prefix_iterator;

	bool auth;

protected:
	std::vector<std::string> Prefixs;
};

/// Text file based IP authentication
class FileIPAuth : public IPAuthBase {
public:
	typedef std::pair<NetworkAddress, IPAuthPrefix> IPAuthEntry;

	
	/// Create text file based authenticator
	FileIPAuth( 
		/// authenticator name from Gatekeeper::Auth section
		const char *authName
		);

	/// Destroy the authenticator
	virtual ~FileIPAuth();

protected:
	/// Overriden from IPAuthBase
	virtual int CheckAddress(
		const PIPSocket::Address &addr, /// IP address the request comes from
		WORD port, /// port number the request comes from
		const PString &number
		);
				
private:
	FileIPAuth();
	/* No copy constructor allowed */
	FileIPAuth(const FileIPAuth&);
	/* No operator= allowed */
	FileIPAuth& operator=(const FileIPAuth&);
	
private:
	typedef std::vector<IPAuthEntry> IPAuthList;

	IPAuthList m_authList;
};


IPAuthBase::IPAuthBase( 
	/// authenticator name from Gatekeeper::Auth section
	const char *authName,
	/// bitmask with supported RAS checks
	unsigned supportedRasChecks,
	/// bitmask with supported non-RAS checks
	unsigned supportedMiscChecks
	) : GkAuthenticator(authName, supportedRasChecks, supportedMiscChecks)
{
}

IPAuthBase::~IPAuthBase()
{
}

int IPAuthBase::Check(
	/// GRQ RAS message to be authenticated
	RasPDU<H225_GatekeeperRequest> &grqPdu, 
	/// gatekeeper request reject reason
	unsigned &rejectReason
	)
{
	return CheckAddress(grqPdu->m_peerAddr, grqPdu->m_peerPort, PString());
}

int IPAuthBase::Check(
	/// RRQ RAS message to be authenticated
	RasPDU<H225_RegistrationRequest> &rrqPdu, 
	/// authorization data (reject reason, ...)
	RRQAuthData &authData
	)
{
	return CheckAddress(rrqPdu->m_peerAddr, rrqPdu->m_peerPort, PString());
}

int IPAuthBase::Check(
	/// LRQ nessage to be authenticated
	RasPDU<H225_LocationRequest> &lrqPdu, 
	/// location request reject reason
	unsigned &rejectReason
	)
{
	return CheckAddress(lrqPdu->m_peerAddr, lrqPdu->m_peerPort, PString());
}

int IPAuthBase::Check(
	/// Q.931/H.225 Setup message to be authenticated
	SetupMsg &setup,
	/// authorization data (call duration limit, reject reason, ...)
	SetupAuthData& authData
	)
{
	PIPSocket::Address addr;
	WORD port = 0;
	setup.GetPeerAddr(addr, port);
	PString number;
	setup.GetQ931().GetCalledPartyNumber(number);

	return CheckAddress(addr, port, number);
}


/////////////
// FileIPAuth
/////////////

namespace {
const char *FileIPAuthSecName = "FileIPAuth";

struct IPAuthEntry_greater : public binary_function<FileIPAuth::IPAuthEntry, FileIPAuth::IPAuthEntry, bool> {

	bool operator()(
		const FileIPAuth::IPAuthEntry &a,
		const FileIPAuth::IPAuthEntry &b
		) const 
	{
		const int diff = a.first.Compare(b.first);
		if (diff == 0)
			return !a.second.auth;
		return diff > 0;
	}
};

} /* anonymous namespace */

FileIPAuth::FileIPAuth( 
	/// authenticator name from Gatekeeper::Auth section
	const char *authName
	) : IPAuthBase(authName)
{
	bool dynamicCfg = false;
	PConfig *cfg = GkConfig();
	
	if (cfg->HasKey(FileIPAuthSecName, "include")) {
		const PFilePath fp(cfg->GetString(FileIPAuthSecName, "include", ""));
		if (!PFile::Exists(fp)) {
			PTRACE(0, GetName() << "\tCould not read the include file '"
				<< fp << "': the file does not exist"
				);
			return;
		}
		cfg = new PConfig(fp, authName);
		dynamicCfg = true;
	}

	PStringToString kv = cfg->GetAllKeyValues(FileIPAuthSecName);
	
	m_authList.reserve(kv.GetSize());
	
	for (PINDEX i = 0; i < kv.GetSize(); i++) {
		const PString &key = kv.GetKeyAt(i);
		int position = 0;
		if (key[0] == '#')
			continue;
		
		IPAuthEntry entry;
		
		entry.first = (key == "*" || key == "any") ? NetworkAddress() : NetworkAddress(key);

		PString auth(kv.GetDataAt(i));
		PString prefix(".");

                if ((position = auth.Find(';', position)) != P_MAX_INDEX) {
            		position++;
                        prefix = auth(position, auth.GetLength());
                        position--;
			auth.Delete(position, auth.GetLength() - position);
		}

		entry.second.auth = PCaselessString("allow") == auth;
		entry.second.AddPrefix(prefix);
		entry.second.SortPrefix(false);
		
		m_authList.push_back(entry);
	}

	std::stable_sort(m_authList.begin(), m_authList.end(), IPAuthEntry_greater());
	
	PTRACE(5, GetName() << "\t" << m_authList.size() << " entries loaded");

#if PTRACING
	if (PTrace::CanTrace(6)) {
		ostream &strm = PTrace::Begin(6, __FILE__, __LINE__);
		strm << GetName() << " entries:\n";
		IPAuthList::const_iterator entry = m_authList.begin();
		while (entry != m_authList.end()) {
			strm << "\t" << entry->first.AsString() << " = " 
				<< (entry->second.auth ? "allow" : "reject")
				<< (entry->second.auth ? entry->second.PrintOn() : "") << endl;
			entry++;
		}
		PTrace::End(strm);
	}
#endif

	if (dynamicCfg)
		delete cfg;
}

FileIPAuth::~FileIPAuth()
{
}

int FileIPAuth::CheckAddress(
	const PIPSocket::Address &addr, /// IP address the request comes from
	WORD port, /// port number the request comes from
	const PString &number
	)
{
	IPAuthList::const_iterator entry = m_authList.begin();
	while (entry != m_authList.end()) {
		if (entry->first.IsAny() || addr << entry->first) {
			if (entry->second.auth && !number.IsEmpty()) {
				int len = entry->second.PrefixMatch(number);
				PTRACE(5, GetName() << "\tIP " << addr.AsString() 
					<< (len ? " accepted" : " rejected")
					<< " for Called " << number
					);
				return len ? e_ok : e_fail;
			}
			return entry->second.auth ? e_ok : e_fail;
		}
		entry++;
	}
	return GetDefaultStatus();
}


IPAuthPrefix::IPAuthPrefix() :
	auth(false)
{}

IPAuthPrefix::IPAuthPrefix(bool a, const PString & prefixes)
{
	auth = a;
	AddPrefix(prefixes);
}

IPAuthPrefix::IPAuthPrefix(const IPAuthPrefix & obj)
{
	auth = obj.auth;

	const_prefix_iterator Iter = obj.Prefixs.begin();
	const_prefix_iterator eIter = obj.Prefixs.end();
	while (Iter != eIter) {
		Prefixs.push_back(Iter->c_str());
		++Iter;
	}
}

IPAuthPrefix& IPAuthPrefix::operator=(const IPAuthPrefix & obj)
{
	auth = obj.auth;
	Prefixs.clear();

	const_prefix_iterator Iter = obj.Prefixs.begin();
	const_prefix_iterator eIter = obj.Prefixs.end();
	while (Iter != eIter) {
		Prefixs.push_back(Iter->c_str());
		++Iter;
	}

	return *this;
}

IPAuthPrefix& IPAuthPrefix::operator=(bool a)
{
	auth = a;
	return *this;
}

void IPAuthPrefix::AddPrefix(const PString & prefixes)
{
	PStringArray p(prefixes.Tokenise(" ,;\t\n", false));
	for (PINDEX i = 0; i < p.GetSize(); ++i)
		Prefixs.push_back((const char *)p[i]);
}

void IPAuthPrefix::SortPrefix(bool greater)
{
	// remove duplicate aliases
	if (greater)
		sort(Prefixs.begin(), Prefixs.end(), str_prefix_greater());
	else
		sort(Prefixs.begin(), Prefixs.end(), str_prefix_lesser());
	prefix_iterator Iter = std::unique(Prefixs.begin(), Prefixs.end());
	Prefixs.erase(Iter, Prefixs.end());
}

int IPAuthPrefix::PrefixMatch(const PString & number) const
{
	if (number.IsEmpty())
		return 0;

	const char * alias = (const char*)(number);

	if (!alias)
		return 0;

	const_prefix_iterator Iter = Prefixs.begin();
	const_prefix_iterator eIter = Prefixs.end();

	if (Iter == eIter)
		return 1;

	while (Iter != eIter) {
		const int len = MatchPrefix(alias, Iter->c_str());
		if (len > 0) {
			return len;
		}
		++Iter;
	}

	return 0;
}

std::string IPAuthPrefix::PrintOn(void) const
{
	if (!auth)
		return std::string(" to called any");

	std::string prefix = PrintPrefix();

	if (prefix == ".")
		prefix = "any";
	std::string ret(" to called ");
	ret += prefix;
	return ret;
}

std::string IPAuthPrefix::PrintPrefix(void) const
{
	std::string prefix;
	const_prefix_iterator Iter = Prefixs.begin();
	const_prefix_iterator eIter = Prefixs.end();
	while (Iter != eIter) {
		prefix += *Iter;
		prefix += ",";
		++Iter;
	}

	return prefix;
}
				
namespace { // anonymous namespace
	GkAuthCreator<FileIPAuth> FileIPAuthCreator("FileIPAuth");
} // end of anonymous namespace


syntax highlighted by Code2HTML, v. 0.9.1