Example #1
0
bool CClient::OnPartMessage(CPartMessage& Message) {
    CString sChans = Message.GetTarget();

    VCString vsChans;
    sChans.Split(",", vsChans, false);
    sChans.clear();

    for (CString& sChan : vsChans) {
        bool bContinue = false;
        Message.SetTarget(sChan);
        NETWORKMODULECALL(OnUserPartMessage(Message), m_pUser, m_pNetwork, this,
                          &bContinue);
        if (bContinue) continue;

        sChan = Message.GetTarget();

        CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChan) : nullptr;

        if (pChan && !pChan->IsOn()) {
            PutStatusNotice("Removing channel [" + sChan + "]");
            m_pNetwork->DelChan(sChan);
        } else {
            sChans += (sChans.empty()) ? sChan : CString("," + sChan);
        }
    }

    if (sChans.empty()) {
        return true;
    }

    Message.SetTarget(sChans);

    return false;
}
Example #2
0
bool CClient::OnJoinMessage(CJoinMessage& Message) {
    CString sChans = Message.GetTarget();
    CString sKeys = Message.GetKey();

    VCString vsChans;
    sChans.Split(",", vsChans, false);
    sChans.clear();

    VCString vsKeys;
    sKeys.Split(",", vsKeys, true);
    sKeys.clear();

    for (unsigned int a = 0; a < vsChans.size(); a++) {
        Message.SetTarget(vsChans[a]);
        Message.SetKey((a < vsKeys.size()) ? vsKeys[a] : "");
        bool bContinue = false;
        NETWORKMODULECALL(OnUserJoinMessage(Message), m_pUser, m_pNetwork, this,
                          &bContinue);
        if (bContinue) continue;

        CString sChannel = Message.GetTarget();
        CString sKey = Message.GetKey();

        CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChannel) : nullptr;
        if (pChan) {
            if (pChan->IsDetached())
                pChan->AttachUser(this);
            else
                pChan->JoinUser(sKey);
            continue;
        }

        if (!sChannel.empty()) {
            sChans += (sChans.empty()) ? sChannel : CString("," + sChannel);

            if (!vsKeys.empty()) {
                sKeys += (sKeys.empty()) ? sKey : CString("," + sKey);
            }
        }
    }

    Message.SetTarget(sChans);
    Message.SetKey(sKeys);

    return sChans.empty();
}
Example #3
0
File: Modules.cpp Project: md-5/znc
ModHandle CModules::OpenModule(const CString& sModule, const CString& sModPath, bool &bVersionMismatch,
		CModInfo& Info, CString& sRetMsg) {
	// Some sane defaults in case anything errors out below
	bVersionMismatch = false;
	sRetMsg.clear();

	for (unsigned int a = 0; a < sModule.length(); a++) {
		if (((sModule[a] < '0') || (sModule[a] > '9')) && ((sModule[a] < 'a') || (sModule[a] > 'z')) && ((sModule[a] < 'A') || (sModule[a] > 'Z')) && (sModule[a] != '_')) {
			sRetMsg = "Module names can only contain letters, numbers and underscores, [" + sModule + "] is invalid.";
			return NULL;
		}
	}

	// The second argument to dlopen() has a long history. It seems clear
	// that (despite what the man page says) we must include either of
	// RTLD_NOW and RTLD_LAZY and either of RTLD_GLOBAL and RTLD_LOCAL.
	//
	// RTLD_NOW vs. RTLD_LAZY: We use RTLD_NOW to avoid ZNC dying due to
	// failed symbol lookups later on. Doesn't really seem to have much of a
	// performance impact.
	//
	// RTLD_GLOBAL vs. RTLD_LOCAL: If perl is loaded with RTLD_LOCAL and later on
	// loads own modules (which it apparently does with RTLD_LAZY), we will die in a
	// name lookup since one of perl's symbols isn't found. That's worse
	// than any theoretical issue with RTLD_GLOBAL.
	ModHandle p = dlopen((sModPath).c_str(), RTLD_NOW | RTLD_GLOBAL);

	if (!p) {
		// dlerror() returns pointer to static buffer, which may be overwritten very soon with another dl call
		// also it may just return null.
		const char* cDlError = dlerror();
		CString sDlError = cDlError ? cDlError : "Unknown error";
		sRetMsg = "Unable to open module [" + sModule + "] [" + sDlError + "]";
		return NULL;
	}

	typedef bool (*InfoFP)(double, CModInfo&);
	InfoFP ZNCModInfo = (InfoFP) dlsym(p, "ZNCModInfo");

	if (!ZNCModInfo) {
		dlclose(p);
		sRetMsg = "Could not find ZNCModInfo() in module [" + sModule + "]";
		return NULL;
	}

	if (ZNCModInfo(CModule::GetCoreVersion(), Info)) {
		sRetMsg = "";
		bVersionMismatch = false;
	} else {
		bVersionMismatch = true;
		sRetMsg = "Version mismatch, recompile this module.";
	}

	return p;
}
Example #4
0
File: Message.cpp Project: BtbN/znc
void CMessage::Parse(CString sMessage) {
    // <tags>
    m_mssTags.clear();
    if (sMessage.StartsWith("@")) {
        VCString vsTags;
        sMessage.Token(0).TrimPrefix_n("@").Split(";", vsTags, false);
        for (const CString& sTag : vsTags) {
            CString sKey = sTag.Token(0, false, "=", true);
            CString sValue = sTag.Token(1, true, "=", true);
            m_mssTags[sKey] =
                sValue.Escape(CString::EMSGTAG, CString::CString::EASCII);
        }
        sMessage = sMessage.Token(1, true);
    }

    //  <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
    //  <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
    //  <command>  ::= <letter> { <letter> } | <number> <number> <number>
    //  <SPACE>    ::= ' ' { ' ' }
    //  <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
    //  <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
    //                 or NUL or CR or LF, the first of which may not be ':'>
    //  <trailing> ::= <Any, possibly *empty*, sequence of octets not including
    //                   NUL or CR or LF>

    // <prefix>
    if (sMessage.TrimPrefix(":")) {
        m_Nick.Parse(sMessage.Token(0));
        sMessage = sMessage.Token(1, true);
    }

    // <command>
    m_sCommand = sMessage.Token(0);
    sMessage = sMessage.Token(1, true);

    // <params>
    m_bColon = false;
    m_vsParams.clear();
    while (!sMessage.empty()) {
        m_bColon = sMessage.TrimPrefix(":");
        if (m_bColon) {
            m_vsParams.push_back(sMessage);
            sMessage.clear();
        } else {
            m_vsParams.push_back(sMessage.Token(0));
            sMessage = sMessage.Token(1, true);
        }
    }

    InitType();
}
Example #5
0
File: User.cpp Project: johnfb/znc
bool CUser::IsValid(CString& sErrMsg, bool bSkipPass) const {
	sErrMsg.clear();

	if (!bSkipPass && m_sPass.empty()) {
		sErrMsg = "Pass is empty";
		return false;
	}

	if (m_sUserName.empty()) {
		sErrMsg = "Username is empty";
		return false;
	}

	if (!CUser::IsValidUserName(m_sUserName)) {
		sErrMsg = "Username is invalid";
		return false;
	}

	return true;
}
Example #6
0
bool CFile::ReadFile(CString& sData, size_t iMaxSize) {
	char buff[4096];
	size_t iBytesRead = 0;

	sData.clear();

	while (iBytesRead < iMaxSize) {
		ssize_t iBytes = Read(buff, sizeof(buff));

		if (iBytes < 0)
			// Error
			return false;

		if (iBytes == 0)
			// EOF
			return true;

		sData.append(buff, iBytes);
		iBytesRead += iBytes;
	}

	// Buffer limit reached
	return false;
}
Example #7
0
File: User.cpp Project: johnfb/znc
bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) {
	unsigned int a = 0;
	sErrorRet.clear();

	if (!User.IsValid(sErrorRet, true)) {
		return false;
	}

	// user names can only specified for the constructor, changing it later
	// on breaks too much stuff (e.g. lots of paths depend on the user name)
	if (GetUserName() != User.GetUserName()) {
		DEBUG("Ignoring username in CUser::Clone(), old username [" << GetUserName()
				<< "]; New username [" << User.GetUserName() << "]");
	}

	if (!User.GetPass().empty()) {
		SetPass(User.GetPass(), User.GetPassHashType(), User.GetPassSalt());
	}

	SetNick(User.GetNick(false));
	SetAltNick(User.GetAltNick(false));
	SetIdent(User.GetIdent(false));
	SetRealName(User.GetRealName());
	SetStatusPrefix(User.GetStatusPrefix());
	SetBindHost(User.GetBindHost());
	SetDCCBindHost(User.GetDCCBindHost());
	SetQuitMsg(User.GetQuitMsg());
	SetSkinName(User.GetSkinName());
	SetDefaultChanModes(User.GetDefaultChanModes());
	SetBufferCount(User.GetBufferCount(), true);
	SetJoinTries(User.JoinTries());

	// Allowed Hosts
	m_ssAllowedHosts.clear();
	const set<CString>& ssHosts = User.GetAllowedHosts();
	for (set<CString>::const_iterator it = ssHosts.begin(); it != ssHosts.end(); ++it) {
		AddAllowedHost(*it);
	}

	for (a = 0; a < m_vClients.size(); a++) {
		CClient* pSock = m_vClients[a];

		if (!IsHostAllowed(pSock->GetRemoteIP())) {
			pSock->PutStatusNotice("You are being disconnected because your IP is no longer allowed to connect to this user");
			pSock->Close();
		}
	}

	// !Allowed Hosts

	// Networks
	if (bCloneNetworks) {
		CloneNetworks(User);
	}
	// !Networks

	// CTCP Replies
	m_mssCTCPReplies.clear();
	const MCString& msReplies = User.GetCTCPReplies();
	for (MCString::const_iterator it = msReplies.begin(); it != msReplies.end(); ++it) {
		AddCTCPReply(it->first, it->second);
	}
	// !CTCP Replies

	// Flags
	SetAutoClearChanBuffer(User.AutoClearChanBuffer());
	SetMultiClients(User.MultiClients());
	SetDenyLoadMod(User.DenyLoadMod());
	SetAdmin(User.IsAdmin());
	SetDenySetBindHost(User.DenySetBindHost());
	SetTimestampAppend(User.GetTimestampAppend());
	SetTimestampPrepend(User.GetTimestampPrepend());
	SetTimestampFormat(User.GetTimestampFormat());
	SetTimezone(User.GetTimezone());
	// !Flags

	// Modules
	set<CString> ssUnloadMods;
	CModules& vCurMods = GetModules();
	const CModules& vNewMods = User.GetModules();

	for (a = 0; a < vNewMods.size(); a++) {
		CString sModRet;
		CModule* pNewMod = vNewMods[a];
		CModule* pCurMod = vCurMods.FindModule(pNewMod->GetModName());

		if (!pCurMod) {
			vCurMods.LoadModule(pNewMod->GetModName(), pNewMod->GetArgs(), CModInfo::UserModule, this, NULL, sModRet);
		} else if (pNewMod->GetArgs() != pCurMod->GetArgs()) {
			vCurMods.ReloadModule(pNewMod->GetModName(), pNewMod->GetArgs(), this, NULL, sModRet);
		}
	}

	for (a = 0; a < vCurMods.size(); a++) {
		CModule* pCurMod = vCurMods[a];
		CModule* pNewMod = vNewMods.FindModule(pCurMod->GetModName());

		if (!pNewMod) {
			ssUnloadMods.insert(pCurMod->GetModName());
		}
	}

	for (set<CString>::iterator it = ssUnloadMods.begin(); it != ssUnloadMods.end(); ++it) {
		vCurMods.UnloadModule(*it);
	}
	// !Modules

	return true;
}
Example #8
0
void CClient::UserPortCommand(CString& sLine) {
	const CString sCommand = sLine.Token(0);

	if (sCommand.Equals("LISTPORTS")) {
		CTable Table;
		Table.AddColumn("Port");
		Table.AddColumn("BindHost");
		Table.AddColumn("SSL");
		Table.AddColumn("Proto");
		Table.AddColumn("IRC/Web");

		vector<CListener*>::const_iterator it;
		const vector<CListener*>& vpListeners = CZNC::Get().GetListeners();

		for (it = vpListeners.begin(); it < vpListeners.end(); ++it) {
			Table.AddRow();
			Table.SetCell("Port", CString((*it)->GetPort()));
			Table.SetCell("BindHost", ((*it)->GetBindHost().empty() ? CString("*") : (*it)->GetBindHost()));
			Table.SetCell("SSL", CString((*it)->IsSSL()));

			EAddrType eAddr = (*it)->GetAddrType();
			Table.SetCell("Proto", (eAddr == ADDR_ALL ? "All" : (eAddr == ADDR_IPV4ONLY ? "IPv4" : "IPv6")));

			CListener::EAcceptType eAccept = (*it)->GetAcceptType();
			Table.SetCell("IRC/Web", (eAccept == CListener::ACCEPT_ALL ? "All" : (eAccept == CListener::ACCEPT_IRC ? "IRC" : "Web")));
		}

		PutStatus(Table);

		return;
	}

	CString sPort = sLine.Token(1);
	CString sAddr = sLine.Token(2);
	EAddrType eAddr = ADDR_ALL;

	if (sAddr.Equals("IPV4")) {
		eAddr = ADDR_IPV4ONLY;
	} else if (sAddr.Equals("IPV6")) {
		eAddr = ADDR_IPV6ONLY;
	} else if (sAddr.Equals("ALL")) {
		eAddr = ADDR_ALL;
	} else {
		sAddr.clear();
	}

	unsigned short uPort = sPort.ToUShort();

	if (sCommand.Equals("ADDPORT")) {
		CListener::EAcceptType eAccept = CListener::ACCEPT_ALL;
		CString sAccept = sLine.Token(3);

		if (sAccept.Equals("WEB")) {
			eAccept = CListener::ACCEPT_HTTP;
		} else if (sAccept.Equals("IRC")) {
			eAccept = CListener::ACCEPT_IRC;
		} else if (sAccept.Equals("ALL")) {
			eAccept = CListener::ACCEPT_ALL;
		} else {
			sAccept.clear();
		}

		if (sPort.empty() || sAddr.empty() || sAccept.empty()) {
			PutStatus("Usage: AddPort <[+]port> <ipv4|ipv6|all> <web|irc|all> [bindhost]");
		} else {
			bool bSSL = (sPort.Left(1).Equals("+"));
			const CString sBindHost = sLine.Token(4);

			CListener* pListener = new CListener(uPort, sBindHost, bSSL, eAddr, eAccept);

			if (!pListener->Listen()) {
				delete pListener;
				PutStatus("Unable to bind [" + CString(strerror(errno)) + "]");
			} else {
				if (CZNC::Get().AddListener(pListener))
					PutStatus("Port Added");
				else
					PutStatus("Error?!");
			}
		}
	} else if (sCommand.Equals("DELPORT")) {
		if (sPort.empty() || sAddr.empty()) {
			PutStatus("Usage: DelPort <port> <ipv4|ipv6|all> [bindhost]");
		} else {
			const CString sBindHost = sLine.Token(3);

			CListener* pListener = CZNC::Get().FindListener(uPort, sBindHost, eAddr);

			if (pListener) {
				CZNC::Get().DelListener(pListener);
				PutStatus("Deleted Port");
			} else {
				PutStatus("Unable to find a matching port");
			}
		}
	}
}
Example #9
0
bool CIRCNetwork::ParseConfig(CConfig* pConfig, CString& sError,
                              bool bUpgrade) {
    VCString vsList;

    if (!bUpgrade) {
        TOption<const CString&> StringOptions[] = {
            {"nick", &CIRCNetwork::SetNick},
            {"altnick", &CIRCNetwork::SetAltNick},
            {"ident", &CIRCNetwork::SetIdent},
            {"realname", &CIRCNetwork::SetRealName},
            {"bindhost", &CIRCNetwork::SetBindHost},
            {"encoding", &CIRCNetwork::SetEncoding},
            {"quitmsg", &CIRCNetwork::SetQuitMsg},
        };
        TOption<bool> BoolOptions[] = {
            {"ircconnectenabled", &CIRCNetwork::SetIRCConnectEnabled},
            {"trustallcerts", &CIRCNetwork::SetTrustAllCerts},
            {"trustpki", &CIRCNetwork::SetTrustPKI},
        };
        TOption<double> DoubleOptions[] = {
            {"floodrate", &CIRCNetwork::SetFloodRate},
        };
        TOption<short unsigned int> SUIntOptions[] = {
            {"floodburst", &CIRCNetwork::SetFloodBurst},
            {"joindelay", &CIRCNetwork::SetJoinDelay},
        };

        for (const auto& Option : StringOptions) {
            CString sValue;
            if (pConfig->FindStringEntry(Option.name, sValue))
                (this->*Option.pSetter)(sValue);
        }

        for (const auto& Option : BoolOptions) {
            CString sValue;
            if (pConfig->FindStringEntry(Option.name, sValue))
                (this->*Option.pSetter)(sValue.ToBool());
        }

        for (const auto& Option : DoubleOptions) {
            double fValue;
            if (pConfig->FindDoubleEntry(Option.name, fValue))
                (this->*Option.pSetter)(fValue);
        }

        for (const auto& Option : SUIntOptions) {
            unsigned short value;
            if (pConfig->FindUShortEntry(Option.name, value))
                (this->*Option.pSetter)(value);
        }

        pConfig->FindStringVector("loadmodule", vsList);
        for (const CString& sValue : vsList) {
            CString sModName = sValue.Token(0);
            CString sNotice = "Loading network module [" + sModName + "]";

            // XXX Legacy crap, added in ZNC 0.203, modified in 0.207
            // Note that 0.203 == 0.207
            if (sModName == "away") {
                sNotice =
                    "NOTICE: [away] was renamed, loading [awaystore] instead";
                sModName = "awaystore";
            }

            // XXX Legacy crap, added in ZNC 0.207
            if (sModName == "autoaway") {
                sNotice =
                    "NOTICE: [autoaway] was renamed, loading [awaystore] "
                    "instead";
                sModName = "awaystore";
            }

            // XXX Legacy crap, added in 1.1; fakeonline module was dropped in
            // 1.0 and returned in 1.1
            if (sModName == "fakeonline") {
                sNotice =
                    "NOTICE: [fakeonline] was renamed, loading "
                    "[modules_online] instead";
                sModName = "modules_online";
            }

            CString sModRet;
            CString sArgs = sValue.Token(1, true);

            bool bModRet = LoadModule(sModName, sArgs, sNotice, sModRet);

            if (!bModRet) {
                // XXX The awaynick module was retired in 1.6 (still available
                // as external module)
                if (sModName == "awaynick") {
                    // load simple_away instead, unless it's already on the list
                    bool bFound = false;
                    for (const CString& sLoadMod : vsList) {
                        if (sLoadMod.Token(0).Equals("simple_away")) {
                            bFound = true;
                        }
                    }
                    if (!bFound) {
                        sNotice =
                            "Loading network module [simple_away] instead";
                        sModName = "simple_away";
                        // not a fatal error if simple_away is not available
                        LoadModule(sModName, sArgs, sNotice, sModRet);
                    }
                } else {
                    sError = sModRet;
                    return false;
                }
            }
        }
    }

    pConfig->FindStringVector("server", vsList);
    for (const CString& sServer : vsList) {
        CUtils::PrintAction("Adding server [" + sServer + "]");
        CUtils::PrintStatus(AddServer(sServer));
    }

    pConfig->FindStringVector("trustedserverfingerprint", vsList);
    for (const CString& sFP : vsList) {
        AddTrustedFingerprint(sFP);
    }

    pConfig->FindStringVector("chan", vsList);
    for (const CString& sChan : vsList) {
        AddChan(sChan, true);
    }

    CConfig::SubConfig subConf;
    CConfig::SubConfig::const_iterator subIt;

    pConfig->FindSubConfig("chan", subConf);
    for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) {
        const CString& sChanName = subIt->first;
        CConfig* pSubConf = subIt->second.m_pSubConfig;
        CChan* pChan = new CChan(sChanName, this, true, pSubConf);

        if (!pSubConf->empty()) {
            sError = "Unhandled lines in config for User [" +
                     m_pUser->GetUserName() + "], Network [" + GetName() +
                     "], Channel [" + sChanName + "]!";
            CUtils::PrintError(sError);

            CZNC::DumpConfig(pSubConf);
            delete pChan;
            return false;
        }

        // Save the channel name, because AddChan
        // deletes the CChannel*, if adding fails
        sError = pChan->GetName();
        if (!AddChan(pChan)) {
            sError = "Channel [" + sError + "] defined more than once";
            CUtils::PrintError(sError);
            return false;
        }
        sError.clear();
    }

    return true;
}
Example #10
0
void ListServer_Main()
{
	static CBuffer packetBuffer;
	CStringList lines;

	if ( listServerFields[5] == "localhost" ) return;
	if (!lsConnected) return;

	// Read any new data into the socket.
	if ( listServer.getData() == -1 )
	{
		errorOut( "serverlog.txt", "Disconnected from list server." );
		lsConnected = false;
		return;
	}

	// Grab all the data from the socket buffer.
	packetBuffer << listServer.getBuffer();
	listServer.getBuffer().clear();

	// Search for a packet.  If none is found, break out of the loop.
	while (packetBuffer.length() != 0)
	{
		CPacket line;
		if (!nextIsRaw)
		{
			int lineEnd = packetBuffer.find( '\n' );
			if ( lineEnd == -1 ) return;

			// Copy the packet out and remove the \n
			line = packetBuffer.copy( 0, lineEnd + 1 );
			packetBuffer.remove(0, line.length());
			line.remove(line.length() - 1, 1);
		}
		else
		{
			if (packetBuffer.length() < rawPacketSize) return;
			line.writeBytes(packetBuffer.readChars(rawPacketSize), rawPacketSize);
			packetBuffer.remove(0, line.length());
			line.remove(line.length() - 1, 1);
			nextIsRaw = false;
		}
		packetBuffer.setRead(0);

		int messageId = line.readByte1();
		switch (messageId)
		{
			case GSVOLD:
			{
				printf("[%s] SERVER VERSION CHECK - Current: %i, Latest: %i - Old version, please upgrade.\n", getTimeStr(1).text(), GSERVER_BUILD, line.readByte2());
				break;
			}

			case GSVCURRENT:
			{
				printf("[%s] SERVER VERSION CHECK - Current: %i, Latest: %i - You are up to date :)\n", getTimeStr(1).text(), GSERVER_BUILD, GSERVER_BUILD);
				break;
			}

			case GSVACCOUNT:
			{
				CString accountName = line.readChars(line.readByte1());
				CString errorMsg = line.readString("");

				for (int i = 0; i < newPlayers.count(); i++)
				{
					CPlayer *player = (CPlayer *)newPlayers[i];

					if (player->accountName.comparei(accountName) == 0)
					{
						// The serverlist will return case sensitive account names.
						// This helps case sensitive file systems open/save the correct
						// acount.
						player->accountName = accountName;
						if (errorMsg == "SUCCESS")
						{
							player->sendAccount();
						}
						else
						{
							player->sendPacket(CPacket() << (char)DISMESSAGE << errorMsg);
							player->deleteMe = true;
						}

						break;
					}
				}

				break;
			}

			case GSVGUILD:
			{
				int playerId = line.readByte2();
				CPlayer *player = (CPlayer *)playerIds[playerId];

				if (player != NULL)
				{
					CString nick = line.readChars((unsigned char)line.readByte1());
					CString guild = nick.copy( nick.findl( '(' ) ).remove( ")" );

					if ( globalGuilds == false )
					{
						if ( globalGuildList.find( guild ) != -1 )
							player->setNick(nick, false);
					}
					else
						player->setNick(nick, false);
				}

				break;
			}

			case GSVPROFILE: /* Unsure if this works, temp */
			{
				CPacket profile;
				CPlayer *player1 = (CPlayer *)playerIds[line.readByte2()];
				CPlayer *player2 = findPlayerId(line.readChars(line.readByte1()));
				if (player1 == NULL || player2 == NULL)
					return;

				profile << (char)player2->accountName.length() << player2->accountName << line.readString("");

				int time = player2->onlineSecs;
				CString line2;
				//Online time
				line2 << toString((int)time/3600) << " hrs ";
				line2 << toString((int)(time/60)%60) << " mins ";
				line2 << toString((int)time%60) << " secs";
				profile << (char)line2.length() << line2;

				for (int i = 0; i < profileList.count(); i++)
				{
					CStringList a;
					a.load(profileList[i].text(), ":=");
					if (a[0].length() < 1)
						continue;

					CString n;

					if (a[1] == "playerkills")
						n = toString(player2->kills);
					else if (a[1] == "playerdeaths")
						n = toString(player2->deaths);
					else if (a[1] == "playerfullhearts")
					{
						if ( (float)(int)player2->maxPower == (float)player2->maxPower )
							n = toString((int)player2->maxPower);
						else
						{
							n = toString(player2->maxPower);
							n = n.copy( 0, n.length() - 1 );
						}
					}
					else if (a[1] == "playerrating")
						n = toString((int)player2->rating) << "/" << toString((int)player2->deviation);
					else if (a[1] == "playerap")
						n = toString(player2->ap);
					else if (a[1] == "playerrupees")
						n = toString(player2->rubins);
					else if (a[1] == "playerswordpower")
						n = toString(player2->swordPower);
					else if (a[1] == "canspin")
						n = (player2->status & 64 ? "true" : "false");
					else if (a[1] == "playerhearts")
					{
						if ( (float)(int)player2->power == (float)player2->power )
							n = toString((int)player2->power);
						else
						{
							n = toString(player2->power);
							n = n.copy( 0, n.length() - 1 );
						}
					}
					else if (a[1] == "playerdarts")
						n = toString(player2->darts);
					else if (a[1] == "playerbombs")
						n = toString(player2->bombs);
					else if (a[1] == "playermp")
						n = toString(player2->magicPoints);
					else if (a[1] == "playershieldpower")
						n = toString(player2->shieldPower);
					else if (a[1] == "playerglovepower")
						n = toString(player2->glovePower);
					else
					{
						for (int i = 0; i < player2->myFlags.count(); i++)
						{
							CStringList b;
							b.load(player2->myFlags[i].text(), "=");
							if (b[0] == a[1])
							{
								n = b[1];
								break;
							}
						}
					}

					profile << (char)(a[0].length() + n.length() + 2) << a[0] << ":=" << n;
				}

				player1->sendPacket(CPacket() << (char)DPROFILE << profile);
				break;
			}

			case GSVMSG:
				printf("[%s] %s\n", getTimeStr(1).text(), line.readString(""));
			break;

			case GSVFILESTART3:
			{
				unsigned char pTy = (unsigned char)line.readByte1();
				CString fileData, fileName = CString() << dataDir << "global" << fSep;
				switch (pTy)
				{
					case 0: // head
						fileName << "heads" << fSep;
					break;

					case 1: // body
						fileName << "bodies" << fSep;
					break;

					case 2: // sword
						fileName << "swords" << fSep;
					break;

					case 3: // shield
						fileName << "shields" << fSep;
					break;
				}
				fileName << line.readChars(line.readByte1());
				fileData.save(fileName.text());
				break;
			}

			case GSVFILEDATA3:
			{
				unsigned char pTy = (unsigned char)line.readByte1();
				CString fileData, fileName, newData, shortName;
				shortName = line.readChars(line.readByte1());
				fileName = CString() << dataDir << "global" << fSep;
				switch (pTy)
				{
					case 0: // head
						fileName << "heads" << fSep;
					break;

					case 1: // body
						fileName << "bodies" << fSep;
					break;

					case 2: // sword
						fileName << "swords" << fSep;
					break;

					case 3: // shield
						fileName << "shields" << fSep;
					break;
				}
				fileName << shortName.text();
				newData.writeBytes(line.readChars(line.bytesLeft()), line.bytesLeft());

				fileData.load(fileName.text());
				fileData << newData;
				fileData.save(fileName.text());
				break;
			}

			case GSVFILEEND3:
			{
				CPlayer *player = (CPlayer *)playerIds[line.readByte2()];
				int type = line.readByte1();
				char doCompress = line.readByte1();
				time_t modTime = line.readByte5();
				int fileLength = line.readByte5();
				CString shortName = line.readString("");
				CString fileName = CString() << dataDir << "global" << fSep;
				switch (type)
				{
					case 0: // head
						fileName << "heads" << fSep;
					break;

					case 1: // body
						fileName << "bodies" << fSep;
					break;

					case 2: // sword
						fileName << "swords" << fSep;
					break;

					case 3: // shield
						fileName << "shields" << fSep;
					break;
				}
				fileName << shortName.text();

				// If the file was sent compressed, we need to uncompress it.
				if (doCompress == 1)
				{
					// Open the file so we can uncompress it.
					CString fileData;
					fileData.load(fileName.text());

					// Uncompress the file.
					char* buffer = new char[fileLength];
					memset((void*)buffer, 0, fileLength);
					int cLen = fileLength;
					int error = uncompress((Bytef*)buffer,(uLongf*)&cLen,(const Bytef*)fileData.text(), fileData.length());
					if (error != Z_OK) printf("Failed to decompress file: %s\n", shortName.text());

					// Save the file now.
					fileData.clear();
					fileData.writeBytes(buffer, cLen);
					fileData.save(fileName.text());
					delete [] buffer;
				}

				// Set the file mod time.
				if (setFileModTime(fileName.text(), modTime) == false)
					printf("** [WARNING] Could not set modification time on file %s\n", shortName.text());

				if (player)
				{
					player->fileList.add(new COutFile(shortName, 0));
					switch (type)
					{
						case 0: // head
							player->headImage = shortName;
							player->updateProp(HEADGIF);
						break;

						case 1: // body
							player->bodyImage = shortName;
							player->updateProp(BODYIMG);
						break;

						case 2: // sword
							player->swordImage = shortName;
							player->updateProp(SWORDPOWER);
						break;

						case 3: // shield
							player->shieldImage = shortName;
							player->updateProp(SHIELDPOWER);
						break;
					}
				}
				break;
			}

			case GSVPING:
				// Sent every 60 seconds, do nothing.
			break;

			case GSVRAWDATA:
				nextIsRaw = true;
				rawPacketSize = line.readByte3();
			break;

			default:
				printf("[%s] Invalid List Server Message: %i\n", getTimeStr(1).text(), messageId);
			break;
		}
	}
}