[ROSEV_IRCSYSTEM]

- Allow everybody (also unidentified users) to participate as observers in IRC discussions.
  This feature is configurable per channel.
- Implement basic client statuses (NoStatus and Voice) to distinguish between participating members and observers.
  This requires changing the per-channel client set to a client map (CClient object -> Client status).
- Distinguish between the statuses in logs generated by the built-in LogBot.
- Don't include observers when running a vote using the built-in VoteBot.
- Move the UserPasshashMap from CNickServ to CConfiguration, because we need to check whether the nickname is protected at NICK messages.
  Rename "NickServ_Users.ini" to "Users.ini" to reflect this change.
- Update the documentation accordingly.

svn path=/trunk/rosev_ircsystem/; revision=1269
This commit is contained in:
Colin Finck 2011-03-27 13:35:44 +00:00
parent 77d5571a45
commit 107d4ceef9
18 changed files with 236 additions and 139 deletions

Binary file not shown.

View File

@ -0,0 +1,8 @@
# This file controls whether unidentified users may observe the discussions
# on each channel. They cannot participate in any way.
#
# Just add a <channelname>=true|false line for each channel.
# The default value is false.
openchannel=true
lonelychannel=false

View File

@ -2,6 +2,9 @@
# this channel.
# Just add a <channelname>=<nickname> line for each channel and user.
#
# Every user not listed here can still join as an observer if this option
# has been enabled in Channel_Observers.ini.
#
# The server only allows A-Z, a-z and _ characters for user names.
openchannel=Arthur

View File

@ -2,21 +2,21 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
#include <precomp.h>
CChannel::CChannel(const std::string& Name, const std::string& Topic, const std::set<std::string>& AllowedUsers)
: m_Name(Name), m_Topic(Topic), m_AllowedUsers(AllowedUsers)
CChannel::CChannel(const std::string& Name, const std::string& Topic, const std::set<std::string>& AllowedUsers, bool AllowObservers)
: m_Name(Name), m_Topic(Topic), m_AllowedUsers(AllowedUsers), m_AllowObservers(AllowObservers)
{
}
void
CChannel::AddClient(CClient* Client)
CChannel::AddClient(CClient* Client, CChannel::ClientStatus Status)
{
m_Clients.insert(Client);
m_Clients.insert(std::make_pair(Client, Status));
}
void

View File

@ -2,25 +2,33 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
class CChannel : private boost::noncopyable
{
public:
CChannel(const std::string& Name, const std::string& Topic, const std::set<std::string>& AllowedUsers);
enum ClientStatus
{
NoStatus,
Voice
};
void AddClient(CClient* Client);
CChannel(const std::string& Name, const std::string& Topic, const std::set<std::string>& AllowedUsers, bool AllowObservers);
void AddClient(CClient* Client, CChannel::ClientStatus Status);
bool DoAllowObservers() const { return m_AllowObservers; }
const std::set<std::string>& GetAllowedUsers() const { return m_AllowedUsers; }
const std::set<CClient*>& GetClients() const { return m_Clients; }
const std::map<CClient*, ClientStatus>& GetClients() const { return m_Clients; }
const std::string& GetName() const { return m_Name; }
const std::string& GetTopic() const { return m_Topic; }
void RemoveClient(CClient* Client);
private:
std::set<std::string> m_AllowedUsers;
std::set<CClient*> m_Clients;
bool m_AllowObservers;
std::map<CClient*, ClientStatus> m_Clients;
std::string m_Name;
std::string m_Topic;
};

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -123,6 +123,27 @@ CConfiguration::ReadConfigFiles()
MotdStream.close();
/* Users file */
boost::program_options::parsed_options UsersParsedOptions(boost::program_options::parse_config_file<char>(std::string(m_ConfigPath).append(USERS_FILE).c_str(), NULL, true));
for(std::vector< boost::program_options::basic_option<char> >::const_iterator it = UsersParsedOptions.options.begin(); it != UsersParsedOptions.options.end(); ++it)
{
/* Convert the hexadecimal string passhash into a binary one */
const std::string& HexPasshash = it->value[0];
boost::array<char, SHA512_DIGEST_LENGTH> BinaryPasshash;
if(HexPasshash.length() != 2 * SHA512_DIGEST_LENGTH)
BOOST_THROW_EXCEPTION(Error("Length of a passhash must be 128 characters!") << Passhash_Info(HexPasshash));
for(int i = 0; i < SHA512_DIGEST_LENGTH; ++i)
BinaryPasshash[i] = static_cast<char>(strtol(HexPasshash.substr(2 * i, 2).c_str(), NULL, 16));
/* Lowercase the nickname for comparison purposes */
std::string NicknameLowercased(it->string_key);
std::transform(NicknameLowercased.begin(), NicknameLowercased.end(), NicknameLowercased.begin(), tolower);
m_UserPasshashMap.insert(std::make_pair(NicknameLowercased, BinaryPasshash));
}
/* Sanity checks */
if(m_Name.empty())
BOOST_THROW_EXCEPTION(Error("You need to specify a server name!"));

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -30,6 +30,7 @@ public:
unsigned short GetPort() const { return m_Port; }
const std::string& GetSSLCertificateFile() const { return m_SSLCertificateFile; }
const std::string& GetSSLPrivateKeyFile() const { return m_SSLPrivateKeyFile; }
const std::map< std::string, boost::array<char, SHA512_DIGEST_LENGTH> >& GetUserPasshashMap() const { return m_UserPasshashMap; }
bool ParseParameters(int argc, char* argv[]);
void ReadConfigFiles();
@ -52,5 +53,6 @@ private:
bool m_Verbose;
bool m_UseIPv4;
bool m_UseIPv6;
std::map< std::string, boost::array<char, SHA512_DIGEST_LENGTH> > m_UserPasshashMap;
bool m_UseSSL;
};

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -44,6 +44,25 @@ CIRCServer::_AddAcceptor(const boost::asio::ip::tcp::acceptor::protocol_type& Pr
m_Acceptors.push_back(Acceptor);
}
void
CIRCServer::_CheckForPresetNickname(CNetworkClient* Client)
{
/* If this is a preset nickname, the user needs to identify within a given timeframe. */
const std::map< std::string, boost::array<char, SHA512_DIGEST_LENGTH> >& UserPasshashMap = m_Configuration.GetUserPasshashMap();
if(UserPasshashMap.find(Client->GetNicknameLowercased()) != UserPasshashMap.end())
{
Client->SendNotice(NULL, "This nickname is protected.");
Client->SendNotice(NULL, "Please identify with your password in the next " BOOST_PP_STRINGIZE(IDENTIFY_TIMEOUT) " seconds or you will be disconnected.");
Client->SendNotice(NULL, "Use the command /NS IDENTIFY <password> to do so.");
Client->RestartIdentifyTimer();
}
else
{
/* It isn't, so cancel the identify timer and force the client to respond to regular pings again. */
Client->RestartPingTimer();
}
}
void
CIRCServer::_HandleNewConnection(boost::asio::ip::tcp::acceptor* Acceptor, const boost::system::error_code& ErrorCode)
{
@ -94,10 +113,8 @@ CIRCServer::_WelcomeClient(CNetworkClient* Client)
Due to some reason, ircd-seven and other IRC servers use the nickname instead of a full prefix here. */
Client->SendIRCMessage(boost::str(boost::format(":%1% MODE %1% :+i") % Client->GetNickname()));
/* The client also has to identify within a given timeframe. */
Client->SendNotice(NULL, "Please identify with your password in the next " BOOST_PP_STRINGIZE(IDENTIFY_TIMEOUT) " seconds or you will be disconnected.");
Client->SendNotice(NULL, "Use the command /NS IDENTIFY <password> to do so.");
Client->StartIdentifyTimer();
/* At the end of the initial login, the user should know whether he needs to identify or not. */
_CheckForPresetNickname(Client);
}
void
@ -132,14 +149,14 @@ CIRCServer::DisconnectNetworkClient(CNetworkClient* Client, const std::string& R
{
/* Send a QUIT response to all members of all channels, in which this client is a member of,
but only once for each member. */
const std::set<CClient*>& Clients = (*ChannelIt)->GetClients();
for(std::set<CClient*>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
const std::map<CClient*, CChannel::ClientStatus>& Clients = (*ChannelIt)->GetClients();
for(std::map<CClient*, CChannel::ClientStatus>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
{
if(HandledClients.find(*ClientIt) == HandledClients.end())
if(HandledClients.find(ClientIt->first) == HandledClients.end())
{
/* This client has not received the QUIT message yet */
(*ClientIt)->SendIRCMessage(Response);
HandledClients.insert(*ClientIt);
ClientIt->first->SendIRCMessage(Response);
HandledClients.insert(ClientIt->first);
}
}
@ -213,6 +230,20 @@ CIRCServer::Init()
}
}
/* Check which channels allow observers. */
std::set<std::string> ChannelObserversSet;
boost::program_options::parsed_options ChannelObserversParsedOptions(boost::program_options::parse_config_file<char>(std::string(ConfigPath).append(CHANNEL_OBSERVERS_FILE).c_str(), NULL, true));
for(std::vector< boost::program_options::basic_option<char> >::const_iterator OptionIt = ChannelObserversParsedOptions.options.begin(); OptionIt != ChannelObserversParsedOptions.options.end(); ++OptionIt)
{
/* Lowercase the channel name for comparison */
std::string ChannelNameLowercased(OptionIt->string_key);
std::transform(ChannelNameLowercased.begin(), ChannelNameLowercased.end(), ChannelNameLowercased.begin(), tolower);
if(OptionIt->value[0] == "true")
ChannelObserversSet.insert(ChannelNameLowercased);
}
/* Add the preset channels */
boost::program_options::parsed_options ChannelsParsedOptions(boost::program_options::parse_config_file<char>(std::string(ConfigPath).append(CHANNELS_FILE).c_str(), NULL, true));
if(ChannelsParsedOptions.options.empty())
@ -238,8 +269,12 @@ CIRCServer::Init()
if(ChannelUsersIt == ChannelUsersMap.end())
BOOST_THROW_EXCEPTION(Error("No allowed users were set for this channel!") << ChannelName_Info(OptionIt->string_key));
/* Check whether observers are allowed for this channel. */
std::set<std::string>::const_iterator ChannelObserversIt = ChannelObserversSet.find(ChannelNameLowercased);
bool AllowObservers = (ChannelObserversIt != ChannelObserversSet.end());
/* Insert the channel name lowercased for comparisons, but leave the original spelling in the object */
m_Channels.insert(ChannelNameLowercased, new CChannel(OptionIt->string_key, OptionIt->value[0], ChannelUsersIt->second));
m_Channels.insert(ChannelNameLowercased, new CChannel(OptionIt->string_key, OptionIt->value[0], ChannelUsersIt->second, AllowObservers));
}
/* Set up an acceptor for every IP stack (if desired) */
@ -308,10 +343,11 @@ CIRCServer::ReceiveMessage_JOIN(CClient* Sender, const std::vector<std::string>&
}
}
/* ROSEV-SPECIFIC: Only allow joining after the user has identified. */
if(!Sender->GetUserState().IsIdentified)
/* ROSEV-SPECIFIC: Don't allow joining if the user still has to identify. */
const std::map< std::string, boost::array<char, SHA512_DIGEST_LENGTH> >& UserPasshashMap = m_Configuration.GetUserPasshashMap();
if(UserPasshashMap.find(Sender->GetNicknameLowercased()) != UserPasshashMap.end() && !Sender->GetUserState().IsIdentified)
{
Sender->SendNotice(NULL, "You have to identify before joining a channel!");
Sender->SendNotice(NULL, "Please identify first!");
return;
}
@ -340,27 +376,30 @@ CIRCServer::ReceiveMessage_JOIN(CClient* Sender, const std::vector<std::string>&
}
else
{
/* ROSEV-SPECIFIC: For network clients, only allow joining channels this user is allowed to join */
/* ROSEV-SPECIFIC: Virtual clients and clients on the list of allowed users automatically get voice.
If we don't allow observers, only these clients may even join. */
const std::set<std::string>& AllowedUsers = it->second->GetAllowedUsers();
if(Sender->IsNetworkClient() && AllowedUsers.find(Sender->GetNicknameLowercased()) == AllowedUsers.end())
bool IsVoicedClient = (!Sender->IsNetworkClient() || AllowedUsers.find(Sender->GetNicknameLowercased()) != AllowedUsers.end());
if(!IsVoicedClient && !it->second->DoAllowObservers())
{
Sender->SendNotice(NULL, "You are not on the list of allowed users for this channel!");
Sender->SendNotice(NULL, "You are not allowed to join this channel!");
}
else
{
/* Check if the user is already part of this channel */
const std::set<CClient*>& Clients = it->second->GetClients();
const std::map<CClient*, CChannel::ClientStatus>& Clients = it->second->GetClients();
if(Clients.find(Sender) == Clients.end())
{
/* Join this channel */
it->second->AddClient(Sender);
it->second->AddClient(Sender, (IsVoicedClient ? CChannel::Voice : CChannel::NoStatus));
Sender->AddJoinedChannel(it->second);
/* Send a JOIN response to all channel members (including this new one) */
std::string Response(boost::str(boost::format(":%s JOIN #%s") % Sender->GetPrefix() % it->second->GetName()));
for(std::set<CClient*>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
(*ClientIt)->SendIRCMessage(Response);
for(std::map<CClient*, CChannel::ClientStatus>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
ClientIt->first->SendIRCMessage(Response);
/* Send the results of respective TOPIC and NAMES commands */
std::vector<std::string> CallParameters;
@ -434,15 +473,18 @@ CIRCServer::ReceiveMessage_NAMES(CClient* Sender, const std::vector<std::string>
{
/* Return a string list of joined nicknames.
ROSEV-SPECIFIC: We don't support any operators or voiced people. */
const std::set<CClient*>& Clients = it->second->GetClients();
const std::map<CClient*, CChannel::ClientStatus>& Clients = it->second->GetClients();
std::string NicknameList;
for(std::set<CClient*>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
for(std::map<CClient*, CChannel::ClientStatus>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
{
if(!NicknameList.empty())
NicknameList.append(" ");
NicknameList.append((*ClientIt)->GetNickname());
if(ClientIt->second == CChannel::Voice)
NicknameList.append("+");
NicknameList.append(ClientIt->first->GetNickname());
}
Sender->SendNumericReply(RPL_NAMREPLY) % Channel % NicknameList;
@ -520,15 +562,22 @@ CIRCServer::ReceiveMessage_NICK(CClient* Sender, const std::vector<std::string>&
if(!CurrentNickname.empty())
{
const std::string& CurrentNicknameLowercased = NetSender->GetNicknameLowercased();
std::map<std::string, CClient*>::iterator it = m_Nicknames.find(CurrentNicknameLowercased);
/* ROSEV-SPECIFIC: Don't allow changing the nickname if the user has already identified */
if(it->second->GetUserState().IsIdentified)
{
/* ROSEV-SPECIFIC: Don't allow changing the nickname if the user has already identified */
NetSender->SendNotice(NULL, "You cannot change your nickname after having identified!");
return;
}
/* ROSEV-SPECIFIC: Don't allow changing the nickname if the user has already joined a channel */
if(it->second->GetJoinedChannels().size() > 0)
{
NetSender->SendNotice(NULL, "You cannot change your nickname after having joined a channel!");
return;
}
m_Nicknames.erase(it);
NetSender->SendIRCMessage(boost::str(boost::format(":%s NICK %s") % NetSender->GetPrefix() % NewNickname));
}
@ -537,17 +586,22 @@ CIRCServer::ReceiveMessage_NICK(CClient* Sender, const std::vector<std::string>&
m_Nicknames.insert(std::make_pair(NewNicknameLowercased, NetSender));
NetSender->SetNickname(NewNickname, NewNicknameLowercased);
/* Check if we have already registered with a nickname before */
/* Check if we have already registered with a nickname before. */
CClient::UserState State = NetSender->GetUserState();
if(_IsUserRegistered(State))
return;
{
/* The nickname has just been changed, so we need to recheck whether it's a preset one. */
_CheckForPresetNickname(NetSender);
}
else
{
/* We have not, so store this and send the welcome messages if the USER message has also been sent. */
State.HasSentNickMessage = true;
NetSender->SetUserState(State);
/* We have not, so store this and send the welcome messages */
State.HasSentNickMessage = true;
NetSender->SetUserState(State);
if(_IsUserRegistered(State))
_WelcomeClient(NetSender);
if(State.HasSentUserMessage)
_WelcomeClient(NetSender);
}
}
/**
@ -619,7 +673,7 @@ CIRCServer::ReceiveMessage_PART(CClient* Sender, const std::vector<std::string>&
else
{
/* Check if the user is part of this channel */
const std::set<CClient*>& Clients = it->second->GetClients();
const std::map<CClient*, CChannel::ClientStatus>& Clients = it->second->GetClients();
if(Clients.find(Sender) == Clients.end())
{
Sender->SendNumericReply(ERR_NOTONCHANNEL) % Channel;
@ -630,8 +684,8 @@ CIRCServer::ReceiveMessage_PART(CClient* Sender, const std::vector<std::string>&
ROSEV-SPECIFIC: Messages supplied to the PART command are ignored. */
std::string Response(boost::str(boost::format(":%s PART #%s") % Sender->GetPrefix() % it->second->GetName()));
for(std::set<CClient*>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
(*ClientIt)->SendIRCMessage(Response);
for(std::map<CClient*, CChannel::ClientStatus>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
ClientIt->first->SendIRCMessage(Response);
/* Leave the channel */
it->second->RemoveClient(Sender);
@ -725,11 +779,11 @@ CIRCServer::ReceiveMessage_PRIVMSG(CClient* Sender, const std::vector<std::strin
return;
}
/* Is this client part of this channel? */
const std::set<CChannel*>& JoinedChannels = Sender->GetJoinedChannels();
std::set<CChannel*>::const_iterator JoinedIt = JoinedChannels.find(const_cast<CChannel*>(it->second));
/* ROSEV-SPECIFIC: Only joined clients with voice status may send anything. */
const std::map<CClient*, CChannel::ClientStatus>& Clients = it->second->GetClients();
std::map<CClient*, CChannel::ClientStatus>::const_iterator ClientIt = Clients.find(Sender);
if(JoinedIt == JoinedChannels.end())
if(ClientIt == Clients.end() || ClientIt->second == CChannel::NoStatus)
{
Sender->SendNumericReply(ERR_CANNOTSENDTOCHAN) % Channel;
return;
@ -737,11 +791,10 @@ CIRCServer::ReceiveMessage_PRIVMSG(CClient* Sender, const std::vector<std::strin
/* Send the message to all channel members but not back to this client */
std::string Response(boost::str(boost::format(":%s PRIVMSG #%s :%s") % Sender->GetPrefix() % it->second->GetName() % Parameters[1]));
const std::set<CClient*>& Clients = it->second->GetClients();
for(std::set<CClient*>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
if(*ClientIt != Sender)
(*ClientIt)->SendIRCMessage(Response);
for(std::map<CClient*, CChannel::ClientStatus>::const_iterator ClientIt = Clients.begin(); ClientIt != Clients.end(); ++ClientIt)
if(ClientIt->first != Sender)
ClientIt->first->SendIRCMessage(Response);
}
else
{
@ -838,14 +891,15 @@ CIRCServer::ReceiveMessage_USER(CClient* Sender, const std::vector<std::string>&
/* Modify and check the user state */
CClient::UserState State = NetSender->GetUserState();
if(_IsUserRegistered(State))
return;
if(!_IsUserRegistered(State))
{
/* We have not yet registered, so store this and send the welcome messages if the NICK message has also been sent. */
State.HasSentUserMessage = true;
NetSender->SetUserState(State);
State.HasSentUserMessage = true;
NetSender->SetUserState(State);
if(_IsUserRegistered(State))
_WelcomeClient(NetSender);
if(State.HasSentNickMessage)
_WelcomeClient(NetSender);
}
}
/**

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -47,6 +47,7 @@ private:
inline void _Accept(boost::asio::ip::tcp::acceptor* Acceptor);
void _AddAcceptor(const boost::asio::ip::tcp::acceptor::protocol_type& Protocol);
void _CheckForPresetNickname(CNetworkClient* Client);
void _HandleNewConnection(boost::asio::ip::tcp::acceptor* Acceptor, const boost::system::error_code& ErrorCode);
std::string _HandleSSLPassword() const;
inline bool _IsUserRegistered(const CClient::UserState& ClientUserState);

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -26,7 +26,41 @@ CLogBot::_GetLogTimestamp()
}
void
CLogBot::_LogJoinOrPart(CClient* Sender, const std::vector<std::string>& Parameters, const std::string& LogMessage)
CLogBot::_LogMessage_JOIN(CClient* Sender, const std::vector<std::string>& Parameters)
{
/* Expect a channel as the first and only parameter */
assert(Parameters.size() == 1);
assert(Parameters[0][0] == '#');
/* Lowercase it for searching */
std::string ChannelNameLowercased(Parameters[0].substr(1));
std::transform(ChannelNameLowercased.begin(), ChannelNameLowercased.end(), ChannelNameLowercased.begin(), tolower);
/* Check if we log this channel */
boost::ptr_map<std::string, std::ofstream>::iterator ChannelStreamIt = m_ChannelStreamMap.find(ChannelNameLowercased);
if(ChannelStreamIt == m_ChannelStreamMap.end())
return;
/* Determine the status of this client */
const boost::ptr_map<std::string, CChannel>& Channels = m_IRCServer.GetChannels();
boost::ptr_map<std::string, CChannel>::const_iterator ChannelIt = Channels.find(ChannelNameLowercased);
assert(ChannelIt != Channels.end());
const std::map<CClient*, CChannel::ClientStatus>& Clients = ChannelIt->second->GetClients();
std::map<CClient*, CChannel::ClientStatus>::const_iterator ClientIt = Clients.find(Sender);
assert(ClientIt != Clients.end());
/* Finally log the message */
(*ChannelStreamIt->second) << boost::str(boost::format("%s %s has joined %s%s\n")
% _GetLogTimestamp()
% Sender->GetNickname()
% Parameters[0]
% (ClientIt->second == CChannel::Voice ? " with voice status" : "")
);
}
void
CLogBot::_LogMessage_PART(CClient* Sender, const std::vector<std::string>& Parameters)
{
/* Expect a channel as the first and only parameter */
assert(Parameters.size() == 1);
@ -39,20 +73,7 @@ CLogBot::_LogJoinOrPart(CClient* Sender, const std::vector<std::string>& Paramet
/* Check if we log this channel and add a message in this case */
boost::ptr_map<std::string, std::ofstream>::iterator ChannelStreamIt = m_ChannelStreamMap.find(ChannelNameLowercased);
if(ChannelStreamIt != m_ChannelStreamMap.end())
(*ChannelStreamIt->second) << boost::str(boost::format(LogMessage) % _GetLogTimestamp() % Sender->GetNickname() % Parameters[0]);
}
void
CLogBot::_LogMessage_JOIN(CClient* Sender, const std::vector<std::string>& Parameters)
{
_LogJoinOrPart(Sender, Parameters, "%s %s has joined %s\n");
}
void
CLogBot::_LogMessage_PART(CClient* Sender, const std::vector<std::string>& Parameters)
{
_LogJoinOrPart(Sender, Parameters, "%s %s has left %s\n");
(*ChannelStreamIt->second) << boost::str(boost::format("%s %s has left %s\n") % _GetLogTimestamp() % Sender->GetNickname() % Parameters[0]);
}
void

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -20,7 +20,6 @@ private:
std::map<std::string, void (CLogBot::*)(CClient*, const std::vector<std::string>&)> m_CommandHandlers;
std::string _GetLogTimestamp();
void _LogJoinOrPart(CClient* Sender, const std::vector<std::string>& Parameters, const std::string& LogMessage);
void _LogMessage_JOIN(CClient* Sender, const std::vector<std::string>& Parameters);
void _LogMessage_PART(CClient* Sender, const std::vector<std::string>& Parameters);
void _LogMessage_PRIVMSG(CClient* Sender, const std::vector<std::string>& Parameters);

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -266,6 +266,14 @@ CNetworkClient::Init()
_Receive();
}
void
CNetworkClient::RestartIdentifyTimer()
{
m_Timer.cancel();
m_Timer.expires_from_now(boost::posix_time::seconds(IDENTIFY_TIMEOUT));
m_Timer.async_wait(boost::bind(&CNetworkClient::_IdentifyDeadline, shared_from_this(), boost::asio::placeholders::error));
}
void
CNetworkClient::RestartPingTimer()
{
@ -344,11 +352,3 @@ CNetworkClient::Shutdown()
m_ShutdownCompleted = true;
}
void
CNetworkClient::StartIdentifyTimer()
{
m_Timer.cancel();
m_Timer.expires_from_now(boost::posix_time::seconds(IDENTIFY_TIMEOUT));
m_Timer.async_wait(boost::bind(&CNetworkClient::_IdentifyDeadline, shared_from_this(), boost::asio::placeholders::error));
}

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -14,6 +14,7 @@ public:
void Init();
virtual bool IsInitialized() const = 0;
bool IsNetworkClient() const { return true; }
void RestartIdentifyTimer();
void RestartPingTimer();
void SetNickname(const std::string& Nickname, const std::string& NicknameLowercased);
void SetUserState(const CClient::UserState& NewUserState);
@ -22,7 +23,6 @@ public:
CNumericReplyFormatter SendNumericReply(short Code);
void SendPrivateMessage(CClient* Sender, const std::string& PrivateMessage);
void Shutdown();
void StartIdentifyTimer();
protected:
char m_InputBuffer[2 * IRC_MESSAGE_LENGTH];

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -20,26 +20,6 @@ CNickServ::CNickServ(CIRCServer& IRCServer)
bool
CNickServ::Init()
{
/* Load the NickServ-specific configuration (just the extra Users file for now) */
std::string ConfigFile(m_IRCServer.GetConfiguration().GetConfigPath());
ConfigFile.append(NICKSERV_USERS_FILE);
boost::program_options::parsed_options UsersParsedOptions(boost::program_options::parse_config_file<char>(ConfigFile.c_str(), NULL, true));
for(std::vector< boost::program_options::basic_option<char> >::const_iterator it = UsersParsedOptions.options.begin(); it != UsersParsedOptions.options.end(); ++it)
{
/* Convert the hexadecimal string passhash into a binary one */
const std::string& HexPasshash = it->value[0];
boost::array<char, SHA512_DIGEST_LENGTH> BinaryPasshash;
if(HexPasshash.length() != 2 * SHA512_DIGEST_LENGTH)
BOOST_THROW_EXCEPTION(Error("Length of a passhash must be 128 characters!") << Passhash_Info(HexPasshash));
for(int i = 0; i < SHA512_DIGEST_LENGTH; ++i)
BinaryPasshash[i] = static_cast<char>(strtol(HexPasshash.substr(2 * i, 2).c_str(), NULL, 16));
m_UserPasshashMap.insert(std::make_pair(it->string_key, BinaryPasshash));
}
Info("NickServ is enabled.\n");
return true;
}
@ -72,7 +52,7 @@ CNickServ::_ReceiveCommand_GHOST(CNetworkClient* Sender, const std::vector<std::
return;
}
if(!_VerifyCredentials(Sender, Parameters[0], Parameters[1]))
if(!_VerifyCredentials(Sender, NicknameLowercased, Parameters[1]))
return;
/* The password is correct, so disconnect the user */
@ -152,7 +132,7 @@ CNickServ::_ReceiveCommand_IDENTIFY(CNetworkClient* Sender, const std::vector<st
return;
}
if(!_VerifyCredentials(Sender, Sender->GetNickname(), Parameters[0]))
if(!_VerifyCredentials(Sender, Sender->GetNicknameLowercased(), Parameters[0]))
return;
/* The password is correct, so this user has successfully identified! */
@ -164,15 +144,15 @@ CNickServ::_ReceiveCommand_IDENTIFY(CNetworkClient* Sender, const std::vector<st
}
bool
CNickServ::_VerifyCredentials(CNetworkClient* Sender, const std::string& Nickname, const std::string& Password)
CNickServ::_VerifyCredentials(CNetworkClient* Sender, const std::string& NicknameLowercased, const std::string& Password)
{
/* Find the user */
std::map< std::string, boost::array<char, SHA512_DIGEST_LENGTH> >::const_iterator it = m_UserPasshashMap.find(Nickname);
const std::map< std::string, boost::array<char, SHA512_DIGEST_LENGTH> >& UserPasshashMap = m_IRCServer.GetConfiguration().GetUserPasshashMap();
std::map< std::string, boost::array<char, SHA512_DIGEST_LENGTH> >::const_iterator it = UserPasshashMap.find(NicknameLowercased);
if(it == m_UserPasshashMap.end())
if(it == UserPasshashMap.end())
{
Sender->SendNotice(this, "No password is known for this nickname!");
Sender->SendNotice(this, "Ensure that your nickname is spelled correctly (it is case-sensitive).");
return false;
}

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -16,10 +16,9 @@ public:
private:
std::map<std::string, void (CNickServ::*)(CNetworkClient*, const std::vector<std::string>&)> m_CommandHandlers;
std::map< std::string, boost::array<char, SHA512_DIGEST_LENGTH> > m_UserPasshashMap;
void _ReceiveCommand_GHOST(CNetworkClient* Sender, const std::vector<std::string>& Parameters);
void _ReceiveCommand_HELP(CNetworkClient* Sender, const std::vector<std::string>& Parameters);
void _ReceiveCommand_IDENTIFY(CNetworkClient* Sender, const std::vector<std::string>& Parameters);
bool _VerifyCredentials(CNetworkClient* Sender, const std::string& Nickname, const std::string& Password);
bool _VerifyCredentials(CNetworkClient* Sender, const std::string& NicknameLowercased, const std::string& Password);
};

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -116,29 +116,29 @@ CVoteBot::_ReceiveCommand_START(CClient* Sender)
return;
}
/* Save a copy of the currently present network clients, so that no one else can vote.
/* Collect all clients that may vote. These only include network clients with a status.
If a client leaves during the vote, he is also excluded (see CVoteBot::SendIRCMessage). */
const std::set<CClient*>& Clients = m_Channel->GetClients();
for(std::set<CClient*>::const_iterator it = Clients.begin(); it != Clients.end(); ++it)
const std::map<CClient*, CChannel::ClientStatus>& Clients = m_Channel->GetClients();
for(std::map<CClient*, CChannel::ClientStatus>::const_iterator it = Clients.begin(); it != Clients.end(); ++it)
{
if((*it)->IsNetworkClient())
if(it->first->IsNetworkClient() && it->second != CChannel::NoStatus)
{
/* Preselect the "Abstention" option for the client.
If he casts his vote, it is simply overwritten. */
CVoteBot::VoteParameters VoteParameter = {0};
m_Votes.insert(std::make_pair(*it, VoteParameter));
m_Votes.insert(std::make_pair(it->first, VoteParameter));
/* Inform him about the question and the possible options.
Deliberately use private messages instead of notices as latter ones can appear in channels when using clients like ChatZilla. */
(*it)->SendPrivateMessage(this, std::string(m_CurrentAdminNickname).append(" has set up a vote and I want your opinion."));
(*it)->SendPrivateMessage(this, std::string("Question: ").append(m_Question));
(*it)->SendPrivateMessage(this, "Possible options:");
it->first->SendPrivateMessage(this, std::string(m_CurrentAdminNickname).append(" has set up a vote and I want your opinion."));
it->first->SendPrivateMessage(this, std::string("Question: ").append(m_Question));
it->first->SendPrivateMessage(this, "Possible options:");
for(size_t i = 0; i < m_Options.size(); ++i)
(*it)->SendPrivateMessage(this, boost::str(boost::format(" %u - %s") % i % m_Options[i]));
it->first->SendPrivateMessage(this, boost::str(boost::format(" %u - %s") % i % m_Options[i]));
(*it)->SendPrivateMessage(this, "Please send me the number of your option.");
(*it)->SendPrivateMessage(this, boost::str(boost::format("If you don't answer within %u minutes, your vote will be counted as \"%s\".") % m_TimeLimitInMinutes % m_AbstentionTranslation));
it->first->SendPrivateMessage(this, "Please send me the number of your option.");
it->first->SendPrivateMessage(this, boost::str(boost::format("If you don't answer within %u minutes, your vote will be counted as \"%s\".") % m_TimeLimitInMinutes % m_AbstentionTranslation));
}
}
@ -149,7 +149,7 @@ CVoteBot::_ReceiveCommand_START(CClient* Sender)
}
/* Finally report this to the channel as well */
_SendToChannel(std::string(m_CurrentAdminNickname).append(" has set up a vote and I'm asking all of you in private messages now."));
_SendToChannel(std::string(m_CurrentAdminNickname).append(" has set up a vote and I'm asking all participating members in private messages now."));
/* The vote process officially starts... now! */
m_Timer.expires_from_now(boost::posix_time::minutes(m_TimeLimitInMinutes));

View File

@ -2,7 +2,7 @@
* PROJECT: ReactOS Deutschland e.V. IRC System
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* with the additional exemption that compiling, linking, and/or using OpenSSL is allowed
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2011 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -49,11 +49,12 @@
#endif
#define CHANNELS_FILE PATH_SEPARATOR "Channels.ini"
#define CHANNEL_OBSERVERS_FILE PATH_SEPARATOR "Channel_Observers.ini"
#define CHANNEL_USERS_FILE PATH_SEPARATOR "Channel_Users.ini"
#define LOGBOT_FILE PATH_SEPARATOR "LogBot.ini"
#define MAIN_CONFIG_FILE PATH_SEPARATOR "MainConfig.ini"
#define MOTD_FILE PATH_SEPARATOR "Motd.txt"
#define NICKSERV_USERS_FILE PATH_SEPARATOR "NickServ_Users.ini"
#define USERS_FILE PATH_SEPARATOR "Users.ini"
#define VOTEBOTMANAGER_FILE PATH_SEPARATOR "VoteBotManager.ini"
#define VOTEBOT_INDIVIDUAL_FILE PATH_SEPARATOR "VoteBot_%s.ini"