// ------------------------------------------------
// File : servmgr.cpp
// Date: 4-apr-2002
// Author: giles
// Desc: 
//		Management class for handling multiple servent connections.
//
// (c) 2002 peercast.org
// ------------------------------------------------
// 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.
// ------------------------------------------------

#include <stdlib.h>
#include "servent.h"
#include "servmgr.h"
#include "inifile.h"
#include "stats.h"
#include "peercast.h"

static int debugtest=0;



// -----------------------------------
ServMgr::ServMgr()
{
	int i;
	for(i=0; i<MAX_SERVENTS; i++)
		servents[i].init();

	authType = AUTH_COOKIE;
	cookieList.init();

	startTime = sys->getTime();

	allowServer1 = Servent::ALLOW_ALL;
	allowServer2 = Servent::ALLOW_BROADCAST;

	clearHostCache(ServHost::T_NONE);
	password[0]=0;

	allowGnutella = false;
	useFlowControl = true;

	maxBitrate = MAX_BITRATE;
	maxIncoming = MAX_INCOMING;
	maxOutgoing = MAX_OUTGOING;
	maxTryout = MAX_TRYOUT;
	minConnected = MIN_CONNECTED;
	maxStreams = 0;
	maxPreviewTime = MAX_PREVIEWTIME;
	maxPreviewWait = MAX_PREVIEWWAIT;
	refreshHTML = 5;

	networkID.clear();

	notifyMask = 0xffff;


	tryoutDelay = 10;
	relayBroadcast = 300;
	serventBandwidth = 1000;

	numVersions = 0;

	sessionID.generate();

	isRoot = false;

	forceIP[0]	= 0;		// force server IP

	strcpy(connectHost,"connect1.peercast.org");

	serverHost.fromStrIP("127.0.0.1",DEFAULT_PORT);

	firewalled = FW_UNKNOWN;		
	allowDirect = true;
	autoConnect = true;
	forceLookup = true;
	autoServe = true;
	forceNormal = false;

	queryTTL = 7;

	replyIDs.init(500);	// 500 active packets

	totalStreams = 0;
	firewallTimeout = 30;
	pauseLog = false;
	showLog = 0;

	shutdownTimer = 0;

	downloadURL[0] = 0;
	rootMsg.clear();


	restartServer=false;

	setFilterDefaults();

}

// -----------------------------------
void	ServMgr::addVersion(unsigned int ver)
{
	for(int i=0; i<numVersions; i++)
		if (clientVersions[i] == ver)
		{
			clientCounts[i]++;
			return;
		}

	if (numVersions < MAX_VERSIONS)
	{
		clientVersions[numVersions] = ver;
		clientCounts[numVersions] = 1;
		numVersions++;
	}
}

// -----------------------------------
void ServMgr::setFilterDefaults()
{
	numFilters = 0;

	filters[numFilters].host.fromStrIP("255.255.255.255",0);
	filters[numFilters].flags = ServFilter::F_NETWORK;
	numFilters++;

	filters[numFilters].host.fromStrIP("127.0.0.1",0);
	filters[numFilters].flags = ServFilter::F_PRIVATE|ServFilter::F_NETWORK|ServFilter::F_DIRECT;
	numFilters++;

}
// -----------------------------------
void	ServMgr::setPassiveSearch(unsigned int t)
{
//	if ((t > 0) && (t < 60))
//		t = 60;
//	passiveSearch = t;
}
// -----------------------------------
bool ServMgr::seenHost(Host &h, ServHost::TYPE type,unsigned int time)
{
	time = sys->getTime()-time;

	for(int i=0; i<MAX_HOSTCACHE; i++)
		if (hostCache[i].type == type)
			if (hostCache[i].host.ip == h.ip)
				if (hostCache[i].time >= time)
					return true;
	return false;
}

// -----------------------------------
void ServMgr::addHost(Host &h, ServHost::TYPE type, unsigned int time, GnuID &netid)
{
	int i;
//	if (!h.isValid() || h.loopbackIP())
	if (!h.isValid())
		return;

	ServHost *sh=NULL;

	for(i=0; i<MAX_HOSTCACHE; i++)
		if (hostCache[i].type == type)
			if (hostCache[i].host.ip == h.ip)
				if (hostCache[i].host.port == h.port)
					if (hostCache[i].netid.isSame(netid))
					{
						sh = &hostCache[i];
						break;
					}

	char str[64];
	h.toStr(str);
	h.value = 0;	// make sure dead count is zero
	if (!sh)
	{
		LOG_NETWORK("New host: %s - %s",str,ServHost::getTypeStr(type));


		// find empty slot
		for(i=0; i<MAX_HOSTCACHE; i++)
			if (hostCache[i].type == ServHost::T_NONE)
			{
				sh = &hostCache[i];
				break;
			}

		// otherwise, find oldest host and replace
		if (!sh)
			for(i=0; i<MAX_HOSTCACHE; i++)
				if (hostCache[i].type != ServHost::T_NONE)
				{
					if (sh)
					{
						if (hostCache[i].time < sh->time)
							sh = &hostCache[i];
					}else{
						sh = &hostCache[i];
					}
				}
	}else{
		LOG_NETWORK("Duplicate host: %s - %s",str,ServHost::getTypeStr(type));
	}

	if (sh)
		sh->init(h,type,time,netid);
}
// -----------------------------------
void ServMgr::deadHost(Host &h,ServHost::TYPE t)
{
	for(int i=0; i<MAX_HOSTCACHE; i++)
		if (hostCache[i].type == t)
			if (hostCache[i].host.ip == h.ip)
				if (hostCache[i].host.port == h.port)
					hostCache[i].init();
}
// -----------------------------------
void ServMgr::clearHostCache(ServHost::TYPE type)
{
	for(int i=0; i<MAX_HOSTCACHE; i++)
		if ((hostCache[i].type == type) || (type == ServHost::T_NONE))
			hostCache[i].init();
}
	
// -----------------------------------
unsigned int ServMgr::numHosts(ServHost::TYPE type)
{
	unsigned int cnt = 0;
	for(int i=0; i<MAX_HOSTCACHE; i++)
		if ((hostCache[i].type == type) || (type == ServHost::T_NONE))
			cnt++;
	return cnt;
}
// -----------------------------------
int ServMgr::getNewestServents(Host *hl,int max,Host &rh)
{
	int cnt=0;
	for(int i=0; i<max; i++)
	{
		// find newest host not in list
		ServHost *sh=NULL;
		for(int j=0; j<MAX_HOSTCACHE; j++)
		{
			// find newest servent
			if (hostCache[j].type == ServHost::T_SERVENT)
				if (!(rh.globalIP() && !hostCache[j].host.globalIP()))
				{
					// and not in list already
					bool found=false;
					for(int k=0; k<cnt; k++)
						if (hl[k].isSame(hostCache[j].host))
						{
							found=true; 
							break;
						}

					if (!found)
					{
						if (!sh)
						{
							sh = &hostCache[j];
						}else{
							if (hostCache[j].time > sh->time)
								sh = &hostCache[j];
						}
					}
				}
		}

		// add to list
		if (sh)
			hl[cnt++]=sh->host;
	}
	
	return cnt;
}
// -----------------------------------
ServHost ServMgr::getOutgoingServent(GnuID &netid)
{
	ServHost host;

	Host lh(ClientSocket::getIP(NULL),0);

	// find newest host not in list
	ServHost *sh=NULL;
	for(int j=0; j<MAX_HOSTCACHE; j++)
	{
		ServHost *hc=&hostCache[j];
		// find newest servent not already connected.
		if (hc->type == ServHost::T_SERVENT)
			if (hc->netid.isSame(netid))
			{
				if (!((lh.globalIP() && !hc->host.globalIP()) || lh.isSame(hc->host)))
				{
					if (!findServent(Servent::T_OUTGOING,hc->host,hc->netid))
					{
						if (!sh)
						{
							sh = hc;
						}else{
							if (hc->time > sh->time)
								sh = hc;
						}
					}
				}
			}
	}

	if (sh)
		host = *sh;

	return host;
}
// -----------------------------------
Servent *ServMgr::findOldestServent(Servent::TYPE type)
{
	Servent *oldest=NULL;

	for(int i=0; i<ServMgr::MAX_SERVENTS; i++)
	{
		Servent *s = &servMgr->servents[i];
		if (s->type == type)
			if (s->thread.active)
				if (s->isOlderThan(oldest))
					oldest = s;
	}
	return oldest;
}
// -----------------------------------
Servent *ServMgr::findServent(Servent::TYPE type, Host &host, GnuID &netid)
{
	lock.on();
	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if (s->type == type)
		{
			Host h = s->getHost();
			if (h.isSame(host) && s->networkID.isSame(netid))
			{
				lock.off();
				return s;
			}
		}
	}
	lock.off();
	return NULL;

}

// -----------------------------------
Servent *ServMgr::findServent(unsigned int ip, unsigned short port, GnuID &netid)
{
	lock.on();
	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if (s->type != Servent::T_NONE)
		{
			Host h = s->getHost();
			if ((h.ip == ip) && (h.port == port) && (s->networkID.isSame(netid)))
			{
				lock.off();
				return s;
			}
		}
	}
	lock.off();
	return NULL;

}

// -----------------------------------
Servent *ServMgr::findServent(Servent::TYPE t)
{
	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if (s->type == t)
			return s;
	}
	return NULL;
}
// -----------------------------------
Servent *ServMgr::allocServent()
{
	lock.on();
	Servent *s = findServent(Servent::T_NONE);
	if (s)
		s->type = Servent::T_ALLOCATED;

	lock.off();

	if (!s) 
		return NULL;

	return s;
}
// -----------------------------------
unsigned int ServMgr::numConnected(int type)
{
	unsigned int cnt=0;

	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if (s->thread.active)
			if (s->status == Servent::S_CONNECTED)
				if (s->type == type)
					cnt++;
	}
	return cnt;
}
// -----------------------------------
unsigned int ServMgr::numUsed(int type)
{
	unsigned int cnt=0;

	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if (s->type == type)
			if (s->thread.active)
				cnt++;
	}
	return cnt;
}

// -----------------------------------
unsigned int ServMgr::numStreams(bool all)
{
	unsigned int cnt=0;

	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if (s->thread.active)
			if (s->status == Servent::S_CONNECTED)
				if (s->type == Servent::T_STREAM)
					if (all || !s->isPrivate())
						cnt++;
	}
	return cnt;
}
// -----------------------------------
unsigned int ServMgr::totalOutput(bool all)
{
	unsigned int tot = 0;
	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if (s->status == Servent::S_CONNECTED)
			if (s->type == Servent::T_STREAM)
				if (all || !s->isPrivate())
					if (s->sock)
						tot += s->sock->bytesOutPerSec;
	}

	return tot;
}

// -----------------------------------
unsigned int ServMgr::numOutgoing()
{
	int cnt=0;

	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if ((s->type == Servent::T_INCOMING) ||
		    (s->type == Servent::T_OUTGOING)) 
			cnt++;
	}
	return cnt;
}

// -----------------------------------
bool ServMgr::seenPacket(GnuPacket &p)
{
	for(int i=0; i<MAX_SERVENTS; i++)
	{
		Servent *s = &servents[i];
		if (s->status == Servent::S_CONNECTED)
			if (s->seenIDs.contains(p.id))
				return true;
	}
	return false;
}

// -----------------------------------
int ServMgr::broadcast(GnuPacket &pack,Servent *src)
{
	int cnt=0;
	if (pack.ttl)
	{
		for(int i=0; i<MAX_SERVENTS; i++)
		{
			Servent *s = &servents[i];


			if (s != src)
				if (s->type != Servent::T_STREAM)
					if (s->status == Servent::S_CONNECTED)
						if (!s->seenIDs.contains(pack.id))
						{

							if (src)
								if (!src->networkID.isSame(s->networkID))
									continue;

							if (s->outputPacket(pack,false))
								cnt++;
						}
		}
	}

	LOG_NETWORK("broadcast: %s (%d) to %d servents",GNU_FUNC_STR(pack.func),pack.ttl,cnt);

	return cnt;
}
// -----------------------------------
int ServMgr::route(GnuPacket &pack, GnuID &routeID, Servent *src)
{
	int cnt=0;
	if (pack.ttl)
	{
		for(int i=0; i<MAX_SERVENTS; i++)
		{
			Servent *s = &servents[i];

			if (s != src)
				if (s->type != Servent::T_STREAM)
					if (s->status == Servent::S_CONNECTED)
						if (!s->seenIDs.contains(pack.id))
							if (s->seenIDs.contains(routeID))
							{
								if (src)
									if (!src->networkID.isSame(s->networkID))
										continue;

								if (s->outputPacket(pack,true))
									cnt++;
							}
		}
	}

	LOG_NETWORK("route: %s (%d) to %d servents",GNU_FUNC_STR(pack.func),pack.ttl,cnt);
	return cnt;
}
// -----------------------------------
void ServMgr::setFirewall(FW_STATE state)
{
	if (firewalled != state)
	{
		char *str;
		switch (state)
		{
			case FW_ON:
				str = "ON";
				break;
			case FW_OFF:
				str = "OFF";
				break;
			case FW_UNKNOWN:
			default:
				str = "UNKNOWN";
				break;
		}

		LOG_DEBUG("Firewall is set to %s",str);
		firewalled = state;
	}
}
// -----------------------------------
bool ServMgr::isFiltered(int fl, Host &h)
{
	for(int i=0; i<numFilters; i++)
		if (filters[i].flags & fl)
			if (h.isMemberOf(filters[i].host))
				return true;

	return false;
}

#if 0
// -----------------------------------
bool ServMgr::canServeHost(Host &h)
{
	if (server)
	{
		Host sh = server->getHost();

		if (sh.globalIP() || (sh.localIP() && h.localIP()))
			return true;		
	}
	return false;
}
#endif

// --------------------------------------------------
void writeServerSettings(IniFile &iniFile, unsigned int a)
{
	iniFile.writeBoolValue("allowHTML",a & Servent::ALLOW_HTML);
	iniFile.writeBoolValue("allowData",a & Servent::ALLOW_DATA);
	iniFile.writeBoolValue("allowServent",a & Servent::ALLOW_SERVENT);
	iniFile.writeBoolValue("allowBroadcast",a & Servent::ALLOW_BROADCAST);
}
// --------------------------------------------------
void writeFilterSettings(IniFile &iniFile, ServFilter &f)
{
	char ipstr[64];
	f.host.IPtoStr(ipstr);
	iniFile.writeStrValue("ip",ipstr);
	iniFile.writeBoolValue("private",f.flags & ServFilter::F_PRIVATE);
	iniFile.writeBoolValue("ban",f.flags & ServFilter::F_BAN);
	iniFile.writeBoolValue("network",f.flags & ServFilter::F_NETWORK);
	iniFile.writeBoolValue("direct",f.flags & ServFilter::F_DIRECT);
}

// --------------------------------------------------
static void  writeServHost(IniFile &iniFile, ServHost &sh)
{
	iniFile.writeSection("Host");

	char ipStr[64];
	sh.host.toStr(ipStr);
	iniFile.writeStrValue("type",ServHost::getTypeStr(sh.type));
	iniFile.writeStrValue("address",ipStr);
	iniFile.writeIntValue("time",sh.time);

	if (sh.netid.isSet())
	{
		char netidStr[64];
		sh.netid.toStr(netidStr);
		iniFile.writeStrValue("networkID",netidStr);
	}
	iniFile.writeLine("[End]");
}

// --------------------------------------------------
void ServMgr::saveSettings(const char *fn)
{
	IniFile iniFile;
	if (!iniFile.openWriteReplace(fn))
	{
		LOG_ERROR("Unable to open ini file");
	}else{
		LOG("Saving settings to:  %s",fn);

		char idStr[64];

		iniFile.writeSection("Server");
		iniFile.writeIntValue("serverPort",servMgr->serverHost.port);
		iniFile.writeBoolValue("autoServe",servMgr->autoServe);
		iniFile.writeStrValue("forceIP",servMgr->forceIP);
		iniFile.writeBoolValue("isRoot",servMgr->isRoot);
		iniFile.writeIntValue("maxBitrate",servMgr->maxBitrate);
		iniFile.writeIntValue("maxStreams",servMgr->maxStreams);
		iniFile.writeIntValue("maxStreamsPerChannel",chanMgr->maxStreamsPerChannel);
		iniFile.writeIntValue("maxIncoming",servMgr->maxIncoming);
		iniFile.writeIntValue("maxOutgoing",servMgr->maxOutgoing);
		iniFile.writeIntValue("maxTryout",servMgr->maxTryout);
		iniFile.writeIntValue("minConnected",servMgr->minConnected);
		iniFile.writeIntValue("maxPreviewTime",servMgr->maxPreviewTime);
		iniFile.writeIntValue("maxPreviewWait",servMgr->maxPreviewWait);
		iniFile.writeIntValue("serventBandwidth",servMgr->serventBandwidth);
		iniFile.writeIntValue("firewallTimeout",firewallTimeout);
		iniFile.writeBoolValue("forceNormal",forceNormal);
		iniFile.writeStrValue("rootMsg",rootMsg.cstr());
		iniFile.writeStrValue("authType",servMgr->authType==ServMgr::AUTH_COOKIE?"cookie":"http-basic");
		iniFile.writeStrValue("cookiesExpire",servMgr->cookieList.neverExpire==true?"never":"session");

		networkID.toStr(idStr);
		iniFile.writeStrValue("networkID",idStr);

		iniFile.writeSection("Client");
		iniFile.writeBoolValue("autoConnect",servMgr->autoConnect);
		iniFile.writeStrValue("lookupHost",servMgr->connectHost);
		iniFile.writeIntValue("tryoutDelay",servMgr->tryoutDelay);
		iniFile.writeIntValue("refreshHTML",refreshHTML);
		iniFile.writeIntValue("relayBroadcast",servMgr->relayBroadcast);
		iniFile.writeIntValue("minBroadcastTTL",chanMgr->minBroadcastTTL);
		iniFile.writeIntValue("maxBroadcastTTL",chanMgr->maxBroadcastTTL);
		iniFile.writeIntValue("pushTries",chanMgr->pushTries);
		iniFile.writeIntValue("pushTimeout",chanMgr->pushTimeout);
		iniFile.writeIntValue("maxPushHops",chanMgr->maxPushHops);
		iniFile.writeIntValue("autoQuery",chanMgr->autoQuery);
		iniFile.writeIntValue("queryTTL",servMgr->queryTTL);

		iniFile.writeSection("Privacy");
		iniFile.writeStrValue("password",servMgr->password);
		iniFile.writeIntValue("maxUptime",chanMgr->maxUptime);

		
		int i;

		for(i=0; i<servMgr->numFilters; i++)
		{
			iniFile.writeSection("Filter");
				writeFilterSettings(iniFile,servMgr->filters[i]);
			iniFile.writeLine("[End]");
		}

		iniFile.writeSection("Notify");
			iniFile.writeBoolValue("PeerCast",notifyMask&NT_PEERCAST);
			iniFile.writeBoolValue("Broadcasters",notifyMask&NT_BROADCASTERS);
			iniFile.writeBoolValue("TrackInfo",notifyMask&NT_TRACKINFO);
		iniFile.writeLine("[End]");


		iniFile.writeSection("Server1");
			writeServerSettings(iniFile,allowServer1);
		iniFile.writeLine("[End]");

		iniFile.writeSection("Server2");
			writeServerSettings(iniFile,allowServer2);
		iniFile.writeLine("[End]");


		iniFile.writeSection("Broadcast");
		iniFile.writeIntValue("broadcastMsgInterval",chanMgr->broadcastMsgInterval);
		iniFile.writeStrValue("broadcastMsg",chanMgr->broadcastMsg.cstr());
		iniFile.writeIntValue("icyMetaInterval",chanMgr->icyMetaInterval);

		chanMgr->broadcastID.toStr(idStr);
		iniFile.writeStrValue("broadcastID",idStr);



		
		iniFile.writeIntValue("deadHitAge",chanMgr->deadHitAge);



		iniFile.writeSection("Debug");
		iniFile.writeBoolValue("logDebug",(showLog&(1<<LogBuffer::T_DEBUG))!=0);
		iniFile.writeBoolValue("logErrors",(showLog&(1<<LogBuffer::T_ERROR))!=0);
		iniFile.writeBoolValue("logNetwork",(showLog&(1<<LogBuffer::T_NETWORK))!=0);
		iniFile.writeBoolValue("logChannel",(showLog&(1<<LogBuffer::T_CHANNEL))!=0);
		iniFile.writeBoolValue("pauseLog",pauseLog);
		iniFile.writeIntValue("idleSleepTime",sys->idleSleepTime);


		for(i=0; i<ChanMgr::MAX_CHANNELS; i++)
		{
			char idstr[64];
			Channel *c = &chanMgr->channels[i];
			if (c->isActive())
				if ( ((c->type == Channel::T_RELAY) && (c->stayConnected)) ||
					 ((c->type == Channel::T_BROADCAST) && (c->srcType == Channel::SRC_URL)))
				{
					c->getIDStr(idstr);
					iniFile.writeSection("RelayChannel");
					iniFile.writeStrValue("name",c->getName());
					iniFile.writeStrValue("genre",c->info.genre.cstr());
					if (!c->sourceURL.isEmpty())
						iniFile.writeStrValue("sourceURL",c->sourceURL.cstr());
					iniFile.writeStrValue("sourceProtocol",ChanInfo::getProtocolStr(c->info.srcProtocol));
					iniFile.writeStrValue("contentType",ChanInfo::getTypeStr(c->info.contentType));
					iniFile.writeIntValue("bitrate",c->info.bitrate);
					iniFile.writeStrValue("contactURL",c->info.url.cstr());
					iniFile.writeStrValue("id",idstr);
					iniFile.writeBoolValue("stayConnected",c->stayConnected);
					iniFile.writeLine("[End]");
				}
		}


		

		for(i=0; i<ServMgr::MAX_SERVENTS; i++)
			if (servMgr->servents[i].type == Servent::T_OUTGOING)
				if (servMgr->servents[i].status == Servent::S_CONNECTED)
				{
					ServHost sh;
					Host h = servMgr->servents[i].getHost();
					sh.init(h,ServHost::T_SERVENT,0,servMgr->servents[i].networkID);
					writeServHost(iniFile,sh);
				}

		for(i=0; i<ServMgr::MAX_HOSTCACHE; i++)
		{
			ServHost *sh = &servMgr->hostCache[i];
			if (sh->type != ServHost::T_NONE)
				if (!servMgr->findServent(Servent::T_OUTGOING,sh->host,sh->netid))
					writeServHost(iniFile,*sh);
		}

		iniFile.close();
	}
}
// --------------------------------------------------
unsigned int readServerSettings(IniFile &iniFile, unsigned int a)
{
	while (iniFile.readNext())
	{
		if (iniFile.isName("[End]"))
			break;
		else if (iniFile.isName("allowHTML"))
			a = iniFile.getBoolValue()?a|Servent::ALLOW_HTML:a&~Servent::ALLOW_HTML;
		else if (iniFile.isName("allowData"))
			a = iniFile.getBoolValue()?a|Servent::ALLOW_DATA:a&~Servent::ALLOW_DATA;
		else if (iniFile.isName("allowServent"))
			a = iniFile.getBoolValue()?a|Servent::ALLOW_SERVENT:a&~Servent::ALLOW_SERVENT;
		else if (iniFile.isName("allowBroadcast"))
			a = iniFile.getBoolValue()?a|Servent::ALLOW_BROADCAST:a&~Servent::ALLOW_BROADCAST;
	}
	return a;
}
// --------------------------------------------------
void readFilterSettings(IniFile &iniFile, ServFilter &sv)
{
	sv.host.init();

	while (iniFile.readNext())
	{
		if (iniFile.isName("[End]"))
			break;
		else if (iniFile.isName("ip"))
			sv.host.fromStrIP(iniFile.getStrValue(),0);
		else if (iniFile.isName("private"))
			sv.flags = (sv.flags & ~ServFilter::F_PRIVATE) | (iniFile.getBoolValue()?ServFilter::F_PRIVATE:0);
		else if (iniFile.isName("ban"))
			sv.flags = (sv.flags & ~ServFilter::F_BAN) | (iniFile.getBoolValue()?ServFilter::F_BAN:0);
		else if (iniFile.isName("allow") || iniFile.isName("network"))
			sv.flags = (sv.flags & ~ServFilter::F_NETWORK) | (iniFile.getBoolValue()?ServFilter::F_NETWORK:0);
		else if (iniFile.isName("direct"))
			sv.flags = (sv.flags & ~ServFilter::F_DIRECT) | (iniFile.getBoolValue()?ServFilter::F_DIRECT:0);
	}

}
// --------------------------------------------------
void ServMgr::loadSettings(const char *fn)
{
	IniFile iniFile;

	if (!iniFile.openReadOnly(fn))
		saveSettings(fn);


	servMgr->numFilters = 0;
	showLog = 0;

	if (iniFile.openReadOnly(fn))
	{
		while (iniFile.readNext())
		{
			// server settings
			if (iniFile.isName("serverPort"))
				servMgr->serverHost.port = iniFile.getIntValue();
			else if (iniFile.isName("autoServe"))
				servMgr->autoServe = iniFile.getBoolValue();
			else if (iniFile.isName("autoConnect"))
				servMgr->autoConnect = iniFile.getBoolValue();
			else if (iniFile.isName("icyPassword"))		// depreciated
				strcpy(servMgr->password,iniFile.getStrValue());
			else if (iniFile.isName("forceIP"))
			{
				strcpy(servMgr->forceIP,iniFile.getStrValue());
				if (strlen(servMgr->forceIP))
					servMgr->serverHost.ip = ClientSocket::getIP(servMgr->forceIP);
			}
			else if (iniFile.isName("isRoot"))
				servMgr->isRoot = iniFile.getBoolValue();
			else if (iniFile.isName("broadcastID"))
				chanMgr->broadcastID.fromStr(iniFile.getStrValue());
			else if (iniFile.isName("maxBitrate"))
				servMgr->maxBitrate = iniFile.getIntValue();
			else if (iniFile.isName("maxStreams"))
				servMgr->maxStreams = iniFile.getIntValue();
			else if (iniFile.isName("maxStreamsPerChannel"))
				chanMgr->maxStreamsPerChannel = iniFile.getIntValue();
			else if (iniFile.isName("maxIncoming"))
				servMgr->maxIncoming = iniFile.getIntValue();
			else if (iniFile.isName("maxOutgoing"))
				servMgr->maxOutgoing = iniFile.getIntValue();
			else if (iniFile.isName("maxTryout"))
				servMgr->maxTryout = iniFile.getIntValue();
			else if (iniFile.isName("minConnected"))
				servMgr->minConnected = iniFile.getIntValue();
			else if (iniFile.isName("maxPreviewTime"))
				servMgr->maxPreviewTime = iniFile.getIntValue();
			else if (iniFile.isName("maxPreviewWait"))
				servMgr->maxPreviewWait = iniFile.getIntValue();
			else if (iniFile.isName("firewallTimeout"))
				firewallTimeout = iniFile.getIntValue();
			else if (iniFile.isName("forceNormal"))
				forceNormal = iniFile.getBoolValue();
			else if (iniFile.isName("broadcastMsgInterval"))
				chanMgr->broadcastMsgInterval = iniFile.getIntValue();
			else if (iniFile.isName("broadcastMsg"))
				chanMgr->broadcastMsg.set(iniFile.getStrValue(),String::T_ASCII);
			else if (iniFile.isName("icyMetaInterval"))
				chanMgr->icyMetaInterval = iniFile.getIntValue();
			else if (iniFile.isName("rootMsg"))
				rootMsg.set(iniFile.getStrValue());
			else if (iniFile.isName("networkID"))
				networkID.fromStr(iniFile.getStrValue());
			else if (iniFile.isName("authType"))
			{
				char *t = iniFile.getStrValue();
				if (stricmp(t,"cookie")==0)
					servMgr->authType = ServMgr::AUTH_COOKIE;
				else if (stricmp(t,"http-basic")==0)
					servMgr->authType = ServMgr::AUTH_HTTPBASIC;
			}else if (iniFile.isName("cookiesExpire"))
			{
				char *t = iniFile.getStrValue();
				if (stricmp(t,"never")==0)
					servMgr->cookieList.neverExpire = true;
				else if (stricmp(t,"session")==0)
					servMgr->cookieList.neverExpire = false;


			}

			// privacy settings
			else if (iniFile.isName("password"))
				strcpy(servMgr->password,iniFile.getStrValue());
			else if (iniFile.isName("maxUptime"))
				chanMgr->maxUptime = iniFile.getIntValue();


			// client settings
			else if (iniFile.isName("lookupHost"))
				strcpy(servMgr->connectHost,iniFile.getStrValue());
			else if (iniFile.isName("deadHitAge"))
				chanMgr->deadHitAge = iniFile.getIntValue();
			else if (iniFile.isName("tryoutDelay"))
				servMgr->tryoutDelay = iniFile.getIntValue();
			else if (iniFile.isName("refreshHTML"))
				refreshHTML = iniFile.getIntValue();
			else if (iniFile.isName("relayBroadcast"))
			{
				servMgr->relayBroadcast = iniFile.getIntValue();
				if (servMgr->relayBroadcast < 30)
					servMgr->relayBroadcast = 30;
			}
			else if (iniFile.isName("minBroadcastTTL"))
				chanMgr->minBroadcastTTL = iniFile.getIntValue();
			else if (iniFile.isName("maxBroadcastTTL"))
				chanMgr->maxBroadcastTTL = iniFile.getIntValue();
			else if (iniFile.isName("pushTimeout"))
				chanMgr->pushTimeout = iniFile.getIntValue();
			else if (iniFile.isName("pushTries"))
				chanMgr->pushTries = iniFile.getIntValue();
			else if (iniFile.isName("maxPushHops"))
				chanMgr->maxPushHops = iniFile.getIntValue();
			else if (iniFile.isName("autoQuery"))
			{
				chanMgr->autoQuery = iniFile.getIntValue();
				if ((chanMgr->autoQuery < 300) && (chanMgr->autoQuery > 0))
					chanMgr->autoQuery = 300;
			}
			else if (iniFile.isName("queryTTL"))
			{
				servMgr->queryTTL = iniFile.getIntValue();
			}

			// debug
			else if (iniFile.isName("logDebug"))
				showLog |= iniFile.getBoolValue() ? 1<<LogBuffer::T_DEBUG:0;
			else if (iniFile.isName("logErrors"))
				showLog |= iniFile.getBoolValue() ? 1<<LogBuffer::T_ERROR:0;
			else if (iniFile.isName("logNetwork"))
				showLog |= iniFile.getBoolValue() ? 1<<LogBuffer::T_NETWORK:0;
			else if (iniFile.isName("logChannel"))
				showLog |= iniFile.getBoolValue() ? 1<<LogBuffer::T_CHANNEL:0;
			else if (iniFile.isName("pauseLog"))
				pauseLog = iniFile.getBoolValue();
			else if (iniFile.isName("idleSleepTime"))
				sys->idleSleepTime = iniFile.getIntValue();
			else if (iniFile.isName("[Server1]"))
				allowServer1 = readServerSettings(iniFile,allowServer1);
			else if (iniFile.isName("[Server2]"))
				allowServer2 = readServerSettings(iniFile,allowServer2);
			else if (iniFile.isName("[Filter]"))
			{
				readFilterSettings(iniFile,filters[numFilters]);

				if (numFilters < (MAX_FILTERS-1))
					numFilters++;
			}
			else if (iniFile.isName("[Notify]"))
			{
				notifyMask = NT_UPGRADE;
				while (iniFile.readNext())
				{
					if (iniFile.isName("[End]"))
						break;
					else if (iniFile.isName("PeerCast"))
						notifyMask |= iniFile.getBoolValue()?NT_PEERCAST:0;
					else if (iniFile.isName("Broadcasters"))
						notifyMask |= iniFile.getBoolValue()?NT_BROADCASTERS:0;
					else if (iniFile.isName("TrackInfo"))
						notifyMask |= iniFile.getBoolValue()?NT_TRACKINFO:0;
				}

			}
			else if (iniFile.isName("[RelayChannel]"))
			{
				ChanInfo info;
				bool stayConnected=false;
				String sourceURL;
				while (iniFile.readNext())
				{
					if (iniFile.isName("[End]"))
						break;
					else if (iniFile.isName("name"))
						info.name.set(iniFile.getStrValue());
					else if (iniFile.isName("id"))
						info.id.fromStr(iniFile.getStrValue());
					else if (iniFile.isName("sourceType"))
						info.srcProtocol = ChanInfo::getProtocolFromStr(iniFile.getStrValue());
					else if (iniFile.isName("contentType"))
						info.contentType = ChanInfo::getTypeFromStr(iniFile.getStrValue());
					else if (iniFile.isName("stayConnected"))
						stayConnected = iniFile.getBoolValue();
					else if (iniFile.isName("sourceURL"))
						sourceURL.set(iniFile.getStrValue());
					else if (iniFile.isName("genre"))
						info.genre.set(iniFile.getStrValue());
					else if (iniFile.isName("contactURL"))
						info.url.set(iniFile.getStrValue());
					else if (iniFile.isName("bitrate"))
						info.bitrate = atoi(iniFile.getStrValue());

				}
				if (sourceURL.isEmpty())
				{
					chanMgr->createRelay(info,stayConnected);
				}else
				{
					Channel *c = chanMgr->createChannel(info,NULL);
					if (c)
						c->startURL(sourceURL.cstr());
				}
			}


			// old host cache
			else if (iniFile.isName("[HostCache]") || iniFile.isName("[Servents]"))
			{
				while (iniFile.readNext())
				{
					if (iniFile.isName("[End]"))
						break;
					Host h;
					h.fromStrIP(iniFile.getName(),DEFAULT_PORT);
					GnuID netid;
					netid.clear();
					if (h.isValid())
					{
						char tt[64];
						h.toStr(tt);
						servMgr->addHost(h,ServHost::T_SERVENT,0,netid);
					}
					
				}

			// host 
			}else if (iniFile.isName("[Host]"))
			{
				GnuID netid;
				netid.clear();
				Host h;
				ServHost::TYPE type=ServHost::T_NONE;
				unsigned int time=0;
				while (iniFile.readNext())
				{
					if (iniFile.isName("[End]"))
						break;
					else if (iniFile.isName("address"))
						h.fromStrIP(iniFile.getStrValue(),DEFAULT_PORT);
					else if (iniFile.isName("type"))
						type = ServHost::getTypeFromStr(iniFile.getStrValue());
					else if (iniFile.isName("time"))
						time = iniFile.getIntValue();
					else if (iniFile.isName("networkID"))
						netid.fromStr(iniFile.getStrValue());
				}
				servMgr->addHost(h,type,time,netid);

			}


		}
	}

	if (!numFilters)
		setFilterDefaults();

}
// --------------------------------------------------
int ServMgr::findChannel(ChanInfo &info)
{
	char idStr[64];
	info.id.toStr(idStr);


	if (info.id.isSet())
	{
		// if we have an ID then try and connect to known hosts carrying channel.
		ServHost sh = getOutgoingServent(info.id);
		addOutgoing(sh.host,info.id,true);
	}

	GnuPacket pack;

	XML xml;
	XML::Node *n = info.createQueryXML();
	xml.setRoot(n);
	pack.initFind(NULL,&xml,servMgr->queryTTL);

	addReplyID(pack.id);
	int cnt = broadcast(pack,NULL);

	LOG_NETWORK("Querying network: %s %s - %d servents",info.name.cstr(),idStr,cnt);

	return cnt;
}
// --------------------------------------------------
// add outgoing network connection from string (ip:port format)
bool ServMgr::addOutgoing(Host h, GnuID &netid, bool pri)
{
	if (h.ip)
	{
		if (!findServent(h.ip,h.port,netid))
		{
			Servent *sv = allocServent();
			if (sv)
			{
				if (pri)
					sv->priorityConnect = true;
				sv->networkID = netid;
				sv->initOutgoing(h,Servent::T_OUTGOING);
				return true;
			}
		}
	}
	return false;
}
// --------------------------------------------------
void ServMgr::procConnectArgs(char *str,GnuID &netid)
{
	char arg[512];
	char curr[256];
	char *cmd = strstr(str,"?");


	if (cmd)
	{
		*cmd++ = 0;	// remove "?"

		while (cmd=nextCGIarg(cmd,curr,arg))
		{
			// ip - add direct network connection to client with channel
			if (strcmp(curr,"ip")==0)
			{
				Host h;
				h.fromStrIP(arg,DEFAULT_PORT);
				if (servMgr->addOutgoing(h,servMgr->networkID,true))
					LOG_NETWORK("Added connection: %s",arg);

			}else if (strcmp(curr,"pip")==0)
			{
				Host h;
				h.fromStrIP(arg,DEFAULT_PORT);
				if (servMgr->addOutgoing(h,netid,true))
					LOG_NETWORK("Added private connection: %s",arg);
			}

		}
	}
}

// --------------------------------------------------
bool ServMgr::start()
{
	static ThreadInfo serverThread,clientThread,idleThread;

	serverThread.func = ServMgr::serverProc;
	if (!sys->startThread(&serverThread))
		return false;

	clientThread.func = ServMgr::clientProc;
	if (!sys->startThread(&clientThread))
		return false;

	idleThread.func = ServMgr::idleProc;
	if (!sys->startThread(&idleThread))
		return false;

	return true;
}
// --------------------------------------------------
int ServMgr::clientProc(ThreadInfo *thread)
{
	thread->lock();

	GnuID netID;
	netID = servMgr->networkID;

	while(thread->active)
	{
		if (servMgr->autoConnect)
		{
			if (servMgr->needConnections() || servMgr->forceLookup)
			{
				if (servMgr->needHosts() || servMgr->forceLookup)
				{
					// do lookup to find some hosts

					Host lh;
					lh.fromStrName(servMgr->connectHost,DEFAULT_PORT);


					if (!servMgr->findServent(lh.ip,lh.port,netID))
					{
						Servent *sv = servMgr->allocServent();
						if (sv)
						{
							LOG_DEBUG("Lookup: %s",servMgr->connectHost);
							sv->networkID = netID;
							sv->initOutgoing(lh,Servent::T_LOOKUP);
							servMgr->forceLookup = false;
						}
					}
				}

				for(int i=0; i<MAX_TRYOUT; i++)
				{
					if (servMgr->outUsedFull())
						break;
					if (servMgr->tryFull())
						break;


					ServHost sh = servMgr->getOutgoingServent(netID);

					if (!servMgr->addOutgoing(sh.host,netID,false))
						servMgr->deadHost(sh.host,ServHost::T_SERVENT);
					sys->sleep(servMgr->tryoutDelay);
					break;
				}
			}
		}else{
			for(int i=0; i<ServMgr::MAX_SERVENTS; i++)
			{
				Servent *s = &servMgr->servents[i];
				if (s->type == Servent::T_OUTGOING) 
					s->thread.active = false;

			}
		}
		sys->sleepIdle();
	}
	thread->unlock();
	return 0;
}
// --------------------------------------------------
int ServMgr::idleProc(ThreadInfo *thread)
{

	thread->lock();

	unsigned int lastPasvFind=0;
	unsigned int lastBroadcast=0;


	// nothing much to do for the first couple of seconds, so just hang around.
	sys->sleep(2000);	

	unsigned int lastBWcheck=0;
	unsigned int bytesIn=0,bytesOut=0;

	while(thread->active)
	{
		stats.update();


		if (servMgr->relayBroadcast)
			if ((sys->getTime()-lastBroadcast) > servMgr->relayBroadcast)
			{
				chanMgr->broadcastRelays(NULL,chanMgr->minBroadcastTTL,chanMgr->maxBroadcastTTL);
				lastBroadcast = sys->getTime();
			}

		// auto query network for relays.
		if (chanMgr->autoQuery)
		{
			unsigned int tim = sys->getTime();
			if ((tim-chanMgr->lastQuery) > chanMgr->autoQuery)		
			{
				chanMgr->lastQuery = tim;

				ChanInfo info;
				info.init();			// find all channels
				servMgr->findChannel(info);
			}
		}

		// clear dead hits
		chanMgr->clearDeadHits();

		// kill old idle channels
		{
			int cnt=0;
			Channel *oldest=NULL;
			unsigned int last = (unsigned int )-1;
			for(int i=0; i<ChanMgr::MAX_CHANNELS; i++)
			{
				Channel *c = &chanMgr->channels[i];
				if (c->isIdle())
				{
					cnt++;
					if (c->lastIdleTime < last)
					{
						oldest = c;
						last = c->lastIdleTime;
					}
				}
			}
			if (cnt > ChanMgr::MAX_IDLE)
				if (oldest)
					oldest->thread.active = false;
		}


		// check for too many connections and kill some if needed.
		//if (!servMgr->isRelay)
		{
			char ipStr[64];
			if (servMgr->inOver())
			{
				Servent *s=servMgr->findOldestServent(Servent::T_INCOMING);
				if (s)
				{
					s->getHost().toStr(ipStr);
					LOG_NETWORK("Killing incoming (%d): %s",servMgr->numConnected(Servent::T_INCOMING),ipStr);
					s->thread.active = false;
				}
			}
			if (servMgr->outOver())
			{
				Servent *s=servMgr->findOldestServent(Servent::T_OUTGOING);
				if (s)
				{
					s->getHost().toStr(ipStr);
					LOG_NETWORK("Killing outgoing (%d): %s",servMgr->numConnected(Servent::T_OUTGOING),ipStr);
					s->thread.active = false;
				}
			}
		}

		if (servMgr->shutdownTimer)
		{
			if (--servMgr->shutdownTimer <= 0)
			{
				debugtest = 1;
				peercastInst->saveSettings();
				sys->exit();
			}
		}



		sys->sleep(500);
	}

	thread->unlock();
	return 0;
}

// --------------------------------------------------
int ServMgr::serverProc(ThreadInfo *thread)
{

	thread->lock();

	Servent *serv = servMgr->allocServent();
	serv->permAlloc = true;

	Servent *serv2 = servMgr->allocServent();
	serv2->permAlloc = true;

	unsigned int lastLookupTime=0;


	while (thread->active)
	{
		if (servMgr->restartServer)
		{
			serv->abort();		// force close

			serv2->abort();		// force close

			servMgr->restartServer = false;
		}

		if (servMgr->autoServe)
		{
			serv->allow = servMgr->allowServer1;
			serv2->allow = servMgr->allowServer2;

			if ((serv->lastConnect) && ((sys->getTime()-serv->lastConnect) > servMgr->firewallTimeout))
			{
				if (servMgr->getFirewall()==ServMgr::FW_UNKNOWN)
				{
					LOG_DEBUG("Server has had no incoming connections");
					servMgr->setFirewall(ServMgr::FW_ON);
				}
			}


			// force lookup every 24 hours to check for new messages
			if ((sys->getTime()-lastLookupTime) > 60*60*24)
			{
				servMgr->forceLookup = true;
				lastLookupTime = sys->getTime();
			}


			if ((serv->status == Servent::S_NONE) || (serv2->status == Servent::S_NONE))
			{
				LOG_DEBUG("Starting servers");
				servMgr->forceLookup = true;

				//if (servMgr->serverHost.ip != 0)
				{

					if (servMgr->forceNormal)
						servMgr->setFirewall(ServMgr::FW_OFF);
					else
						servMgr->setFirewall(ServMgr::FW_UNKNOWN);

					Host h = servMgr->serverHost;

					if (serv->status == Servent::S_NONE)
						serv->initServer(h);

					h.port++;
					if (serv2->status == Servent::S_NONE)
						serv2->initServer(h);


				}
			}
		}else{
			// stop server
			serv->abort();		// force close
			serv2->abort();		// force close

			// cancel incoming connectuions
			for(int i=0; i<ServMgr::MAX_SERVENTS; i++)
			{
				Servent *s = &servMgr->servents[i];
				if (s->type == Servent::T_INCOMING)
					s->thread.active = false;
			}

			servMgr->setFirewall(ServMgr::FW_ON);
		}
			
		sys->sleepIdle();

	}

	thread->unlock();
	return 0;
}

// -----------------------------------
XML::Node *ServMgr::createServentXML()
{

	return new XML::Node("servent agent=\"%s\" preview=\"%d\"",
		PCX_AGENT,maxPreviewTime
		);
}

// --------------------------------------------------
const char *ServHost::getTypeStr(TYPE t)
{
	switch(t)
	{
		case T_NONE: return "NONE";
		case T_STREAM: return "STREAM";
		case T_CHANNEL: return "CHANNEL";
		case T_SERVENT: return "SERVENT";
	}
	return "UNKNOWN";
}
// --------------------------------------------------
ServHost::TYPE ServHost::getTypeFromStr(const char *s)
{
	if (stricmp(s,"NONE")==0)
		return T_NONE;
	else if (stricmp(s,"SERVENT")==0)
		return T_SERVENT;
	else if (stricmp(s,"STREAM")==0)
		return T_STREAM;
	else if (stricmp(s,"CHANNEL")==0)
		return T_CHANNEL;

	return T_NONE;
}
