ircd/channel.cpp
#include "channel.hpp"
#include "config.hpp"
#include "client_state.hpp"
#include "irc_std.hpp"
#include "common.hpp"
#include <assert.h>
#define IRC_MAX_TOPIC_LEN 128
#define IRC_MAX_CHANNEL_LEN 16
static bool is_channel(const char* buf, size_t len) {
if (len < 2 || len > IRC_MAX_CHANNEL_LEN || *buf != '#') {
return false;
}
do {
buf++;
len--;
} while (len && (is_letter(*buf) || is_number(*buf) || *buf == '_' || *buf == '-' || *buf == '.'));
return (len == 0);
}
std::map<std::string, Channel*> Channel::channels;
Channel::Channel(const char* _name, bool pers, bool oper, bool ident): chantopic(strdup(_name)), persistent(pers), oper_only(oper), ident_only(ident), name(strdup(_name)) {
assert(channels.find(std::string(name)) == channels.end());
channels.insert(std::pair<std::string, Channel*>(std::string(name), this));
LOG("created channel %s", name);
}
Channel::~Channel() {
LOG("destroying channel %s", name);
assert(clients.empty()); // notify clients thus not needed
assert(channels.find(std::string(name)) != channels.end());
channels.erase(std::string(name));
free((void*)chantopic);
free((void*)name);
}
bool Channel::createInsts() {
for (std::vector<const char*>::const_iterator it = config.channel.strs.begin(); it != config.channel.strs.end(); ++it) {
char* c = strdup(*it);
bool admin = false;
bool ident = false;
if (*c == '@') {
*c = '#';
admin = true;
} else if (*c == '+') {
*c = '#';
ident = true;
}
char* t = strchr(c, ':');
if (t) {
*(t++) = '\0';
}
Channel* channel = Channel::getInst(c, false, true, admin, ident);
if (!channel) {
LOG("cannot create pre-defined channel '%s'", c); // but go on
} else if (t && *t && !channel->topic(t)) {
LOG("cannot set topic for pre-defined channel '%s'", c); // but go on
}
free(c);
}
return true;
}
void Channel::killall() {
while (!channels.empty()) {
std::map<std::string, Channel*>::iterator it = channels.begin();
delete it->second;
}
}
Channel* Channel::getInst(const char* name, bool only_if_exists, bool pers, bool oper, bool ident) {
if (!is_channel(name, strlen(name))) {
return NULL;
}
std::map<std::string, Channel*>::iterator it = channels.find(std::string(name));
if (it != channels.end()) {
return it->second;
} else {
if (only_if_exists) {
return NULL;
}
return new Channel(name, pers, oper, ident);
}
}
void Channel::get_peers(const Client* client, std::set<Client*>& peers) { // TODO: should maintain this?
for (std::set<Channel*>::iterator chit = client->irc_state->channels.begin(); chit != client->irc_state->channels.end(); ++chit) {
for (std::set<Client*>::iterator clit = (*chit)->clients.begin(); clit != (*chit)->clients.end(); ++clit) {
if (*clit != client) peers.insert(*clit); // ensures uniqueness
}
}
}
bool Channel::is_peer(const Client* a, const Client* b) {
for (std::set<Channel*>::iterator chit = a->irc_state->channels.begin(); chit != a->irc_state->channels.end(); ++chit) {
for (std::set<Client*>::iterator clit = (*chit)->clients.begin(); clit != (*chit)->clients.end(); ++clit) {
if (*clit == b) return true;
}
}
return false;
}
bool Channel::join(Client* client) {
// check if permissive
if (clients.find(client) != clients.end()) {
return true; // already in here - ignore
}
if (oper_only && !client->irc_state->is_oper) {
return srv_reply_fmt(client, REPLYFMT(ERR_INVITEONLYCHAN), name, "oper only");
}
if (ident_only && !client->irc_state->nickserv_ident) { // opers are ident, too
return srv_reply_fmt(client, REPLYFMT(ERR_INVITEONLYCHAN), name, "nickserv only");
}
// let join
clients.insert(client);
client->irc_state->channels.insert(this);
// acknowledge/replay and broadcast JOIN
for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) {
(void)cli_reply_fmt(*it, client, "JOIN :%s", name); // ok if not all get it, as error will be cached there
if (*it == client) continue;
const char* mode = client->irc_state->mode_get();
while (*mode) { // also directly broadcast that its an op etc.
(void)cli_reply_fmt(*it, client, "MODE %s +%c %s", name, *mode, client->irc_state->nick);
mode++;
}
}
// topic
if (!srv_reply_fmt(client, REPLYFMT(RPL_TOPIC), name, chantopic)) return false; // RPL_TOPICSETBY?
// names. TODO: could send multiple ones in one line
for (std::set<Client*>::const_iterator it = clients.begin(); it != clients.end(); it++) {
if (!srv_reply_fmt(client, REPLYFMT(RPL_NAMREPLY), name, (*it)->irc_state->state_get(), (*it)->irc_state->nick)) return false;
}
return srv_reply_fmt(client, REPLYFMT(RPL_ENDOFNAMES), name);
}
bool Channel::mode(Client* client) {
const char* mode = client->irc_state->mode_get();
for (std::set<Channel*>::iterator chan_it = client->irc_state->channels.begin(); chan_it != client->irc_state->channels.end(); ++chan_it) {
Channel* channel = *chan_it;
for (std::set<Client*>::iterator cli_it = channel->clients.begin(); cli_it != channel->clients.end(); ++cli_it) {
const char* m = mode;
while (*m) { // also directly broadcast that its an op etc.
if (!cli_reply_fmt(*cli_it, client, "MODE %s +%c %s", channel->name, *m, client->irc_state->nick)) {
if (*cli_it == client) return false;
}
m++;
}
}
}
return true;
}
void Channel::cleanup() {
if (clients.empty() && !persistent) {
delete this;
}
}
void Channel::remove(Client* client) {
std::set<Client*>::iterator it = clients.find(client);
assert(it != clients.end());
if (it == clients.end()) {
return;
}
clients.erase(it);
client->irc_state->channels.erase(this);
cleanup();
}
bool Channel::part(Client* client, const char* msg) {
if (clients.find(client) == clients.end()) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOTONCHANNEL), name);
}
for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) { // so include part'ing client
(void)cli_reply_fmt(*it, client, "PART %s :%s", name, msg ?: "/part");
}
remove(client);
return true;
}
void Channel::disconnect(Client* client, const char* msg) {
std::set<Client*> peers;
get_peers(client, peers);
for (std::set<Client*>::iterator it = peers.begin(); it != peers.end(); ++it) {
(void)cli_reply_fmt(*it, client, "QUIT :%s", msg);
}
for (std::set<Channel*>::iterator it = client->irc_state->channels.begin(); it != client->irc_state->channels.end();) {
(*(it++))->remove(client); // might delete channel, but the iterator should be still valid
}
}
bool Channel::nickchange(const char* old, Client* client) {
if (!raw_reply_fmt(client, ":%s NICK %s", client->irc_state->prefix_get(false, true, old), client->irc_state->nick)) {
return false;
}
std::set<Client*> peers;
get_peers(client, peers);
for (std::set<Client*>::iterator it = peers.begin(); it != peers.end(); ++it) {
(void)raw_reply_fmt(*it, ":%s NICK %s", client->irc_state->prefix_get(true, (*it)->irc_state->is_oper, old), client->irc_state->nick); // TODO: some reply_multi that re-uses buffer?
}
return true;
}
bool Channel::topic(const char* t) {
if (!t || !*t || strlen(t) > IRC_MAX_TOPIC_LEN) {
return false;
}
if (chantopic) {
free((void*)chantopic);
}
chantopic = strdup(t);
return true;
}
bool Channel::topic(Client* client) {
if (clients.find(client) == clients.end()) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOTONCHANNEL), name);
} else {
return srv_reply_fmt(client, REPLYFMT(RPL_TOPIC), name, chantopic);
}
}
bool Channel::topic(Client* client, const char* msg) {
if (clients.find(client) == clients.end()) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOTONCHANNEL), name);
}
if (!client->irc_state->is_oper) {
return srv_reply_fmt(client, REPLYFMT(ERR_CHANOPRIVSNEEDED), name);
}
if (topic(msg)) {
for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) {
(void)cli_reply_fmt(*it, client, "TOPIC %s :%s", name, chantopic);
}
return true;
} else {
return srv_reply_fmt(client, REPLYFMT(ERR_NEEDMOREPARAMS), "topic"); // XXX: better error here?
}
}
bool Channel::who(Client* client) {
if (clients.find(client) == clients.end()) { // not from outside
return srv_reply_fmt(client, REPLYFMT(ERR_NOTONCHANNEL), name);
}
for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) {
if (!srv_reply_fmt(client, REPLYFMT(RPL_WHOREPLY),
name,
(*it)->irc_state->nickserv_ident? "": "~",
(*it)->irc_state->user,
(client == *it || client->irc_state->is_oper)? (*it)->host: Client::dummy_host, // hide IPs
config.server_name.str,
(*it)->irc_state->nick,
(*it)->irc_state->away? 'G': 'H',
(*it)->irc_state->state_get(),
(*it)->irc_state->real
)) return false;
}
return srv_reply_fmt(client, REPLYFMT(RPL_ENDOFWHO), name);
}
bool Channel::list(Client* client) {
if (!srv_reply_msg(client, REPLYMSG(RPL_LISTSTART))) return false;
for (std::map<std::string, Channel*>::const_iterator cit = channels.begin(); cit != channels.end(); ++cit) {
const Channel* c = cit->second;
if (!srv_reply_fmt(client, REPLYFMT(RPL_LIST), c->name, c->clients.size(), c->chantopic)) return false;
}
return srv_reply_msg(client, REPLYMSG(RPL_LISTEND));
}
bool Channel::mode_get(Client* client) {
char modes[16];
char* m = modes;
*(m++) = '+';
*(m++) = 'n'; // no external messages
*(m++) = 't'; // only channel operators may set the channel topic
if (oper_only) {
*(m++) = 'O'; // only allows IRC operators to join
}
if (ident_only) {
*(m++) = 'r'; // regonly: only registered users may join the channel (XXX: and/or 'R'?)
}
if (persistent) {
*(m++) = 'P'; // channel does not disappear when empty
}
#ifdef USE_OPENSSL
if (atoi(config.ssl.str)) {
*(m++) = 'z'; // only allows clients connected via secure connections to join
*(m++) = 'Z'; // only clients connected via secure connections are on the channel
}
#endif
*m = '\0';
return srv_reply_fmt(client, REPLYFMT(RPL_CHANNELMODEIS), name, modes);
}
bool Channel::privmsg(Client* client, const char* msg) {
if (clients.find(client) == clients.end()) {
return srv_reply_fmt(client, REPLYFMT(ERR_CANNOTSENDTOCHAN), name);
}
for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) {
if (*it == client) {
continue; // not for sender himself?
}
(void)raw_reply_fmt_long(*it, msg, ":%s PRIVMSG %s", client->irc_state->prefix_get(true, (*it)->irc_state->is_oper), name);
}
return true;
}