ircd/server.cpp
#include "server.hpp"
#include "client_state.hpp"
#include "channel.hpp"
#include "nickserv.hpp"
#include "irc_std.hpp"
#include "config.hpp"
#include <assert.h>
#include <string>
Server* Server::inst = NULL;
Server::Server() {
assert(!inst);
inst = this;
(void)Channel::createInsts();
}
Server::~Server() {
Client::killall(":*** Shutting down.");
Channel::killall();
}
void Server::disconnect(Client* client, const char* msg) {
Channel::disconnect(client, msg ?: "closing."); // if not yet done
}
bool Server::notice(Client* client, const char* msg) {
return srv_reply_msg(client, "NOTICE", msg, strlen(msg));
}
bool Server::motd(Client* client) const {
return srv_reply_msg(client, REPLYMSG(RPL_WELCOME)) &&
srv_reply_msg(client, REPLYMSG(RPL_MOTDSTART)) &&
#ifdef IRCD_VERSION
srv_reply_msg(client, RPL_MOTD, cstrnlen(": ircd v" STRINGIFY(IRCD_VERSION))) &&
#endif
#ifndef MESSAGE_LOG
srv_reply_msg(client, RPL_MOTD, cstrnlen(": No logs!")) &&
#endif
#ifdef USE_OPENSSL
(atoi(config.ssl.str)? srv_reply_msg(client, RPL_MOTD, cstrnlen(": SSL only!")): true) &&
#endif
(NickServ::is_enabled()? srv_reply_msg(client, RPL_MOTD, cstrnlen(": Please use NickServ to authenticate!")): true) &&
#if 1
srv_reply_msg(client, RPL_MOTD, cstrnlen(": See https://www.hackitu.de/ircd/ for more information.")) &&
#endif
srv_reply_msg(client, REPLYMSG(RPL_ENDOFMOTD));
}
bool Server::ping(Client* client) {
static msg_t ping;
static size_t pinglen = 0;
if (!pinglen) {
pinglen = snprintf(ping, sizeof(ping), "PING :%s", config.server_name.str);
}
return raw_reply_msg(client, ping, pinglen);
}
bool Server::handle(Client* client, Command* cmd) {
// no command, only tickle
if (!cmd) {
return ping(client);
}
// unknown command
if (cmd->type->t == CMD_UNKNOWN) {
return srv_reply_fmt(client, REPLYFMT(ERR_UNKNOWNCOMMAND), cmd->params[0]);
} else if (cmd->nparams < cmd->type->nparams_min || cmd->nparams > cmd->type->nparams_max) {
return srv_reply_fmt(client, REPLYFMT(ERR_NEEDMOREPARAMS), cmd->type->s);
}
// global server auth
switch (cmd->type->t) {
case CMD_PASS:
if (config.server_pw.str && strcmp(cmd->params[0], config.server_pw.str) == 0) {
client->irc_state->has_pass = 1;
return true; // no reply
} else {
return srv_reply_msg(client, REPLYMSG(ERR_PASSWDMISMATCH));
}
default:
break;
}
if (!client->irc_state->has_pass) {
if (!config.server_pw.str) {
client->irc_state->has_pass = 1;
} else {
return srv_reply_fmt(client, REPLYFMT(ERR_NOTREGISTERED), "server password");
}
}
// (pre-)registration commands
switch (cmd->type->t) {
case CMD_NICK:
if (!client->irc_state->nick) { // first time
if (!client->irc_state->nick_set(cmd->params[0])) {
return srv_reply_fmt(client, REPLYFMT(ERR_NICKNAMEINUSE), cmd->params[0]);
} else {
LOG("NICK %s", client->irc_state->nick);
return (client->irc_state->user)? motd(client): true;
}
}
break; // is actually a nickchange - do this below
case CMD_USER:
if (client->irc_state->user) {
return srv_reply_msg(client, REPLYMSG(ERR_ALREADYREGISTRED));
} else if (!client->irc_state->user_set(cmd->params[0], cmd->params[3])) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOTREGISTERED), "invalid");
} else {
return (client->irc_state->nick)? motd(client): true;
}
case CMD_PING:
return raw_reply_fmt(client,
":%s PONG %s :%s",
config.server_name.str, config.server_name.str, (cmd->nparams == 1)? cmd->params[0]: config.server_name.str
);
case CMD_PONG: // Ignored, activity timer has been resetted by client read
return true;
case CMD_CAP: // TODO: extra capabilities really needed?
if (cmd->nparams == 1 && strcmp(cmd->params[0], "LS") == 0) {
return raw_reply_fmt(client, ":%s CAP * LS :", config.server_name.str);
} else if (cmd->nparams == 1 && strcmp(cmd->params[0], "END") == 0) {
return true; // ignore
} else {
return srv_reply_msg(client, REPLYMSG(ERR_INVALIDCAPCMD));
}
case CMD_QUIT:
LOG("QUIT");
Channel::disconnect(client, (cmd->nparams>0)? cmd->params[0]: "/quit");
(void)raw_reply_msg(client, cstrnlen("ERROR :Closing Link: quit"));
return false;
default:
break;
}
// post-login & -registration commands below
if (!client->irc_state->nick || !client->irc_state->user) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOTREGISTERED), "nick/user");
}
// nickserv & pre-ident permissive commands
bool b;
switch (cmd->type->t) {
case CMD_NICKSERV:
b = client->irc_state->nickserv_ident;
if (!NickServ::getInst()->handle(client, (const char**)cmd->params, cmd->nparams)) {
return false;
}
if (!b && client->irc_state->nickserv_ident) {
LOG("NICKSERV ident");
if (!Channel::mode(client)) {
return false;
}
}
return true;
case CMD_OPER:
if (NickServ::is_enabled() && !client->irc_state->nickserv_reg) { // cannot get ident lateron
return srv_reply_fmt(client, REPLYFMT(ERR_NOTREGISTERED), "nickserv unregistered");
}
if (strcmp(client->irc_state->nick, cmd->params[0]) != 0) { // let grant only for himself
return srv_reply_msg(client, REPLYMSG(ERR_PASSWDMISMATCH));
}
if (!config.oper_pw.str || strcmp(config.oper_pw.str, cmd->params[1]) != 0) {
return srv_reply_msg(client, REPLYMSG(ERR_PASSWDMISMATCH));
}
if (!client->irc_state->is_oper) {
client->irc_state->is_oper = true; // might be hidden if not identified, though
LOG("OPER");
if (!Channel::mode(client)) {
return false;
}
}
return srv_reply_msg(client, REPLYMSG(RPL_YOUREOPER));
case CMD_LIST:
return Channel::list(client);
case CMD_AWAY:
(void)client->irc_state->away_set((cmd->nparams == 0)? NULL: cmd->params[0]); // no error reply?
if (client->irc_state->away) {
return srv_reply_msg(client, REPLYMSG(RPL_NOWAWAY));
} else {
return srv_reply_msg(client, REPLYMSG(RPL_UNAWAY));
}
case CMD_MODE:
if (strcmp(cmd->params[0], client->irc_state->nick) == 0) {
// no (own) usermodes except +i (don't globally allow WHO and NAMES anyhow)
return (cmd->nparams == 2 && !strcmp(cmd->params[1], "+i"))? true:
srv_reply_msg(client, REPLYMSG(ERR_UMODEUNKNOWNFLAG));
}
break; // channel modes below
case CMD_MOTD:
return motd(client);
default:
break;
}
// no unident below this point
if (client->irc_state->nickserv_reg && !client->irc_state->nickserv_ident) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOTREGISTERED), "nickserv protected nick");
}
if (client->irc_state->is_oper && NickServ::is_enabled() && !client->irc_state->nickserv_ident) { // oper only for registered users, not checked below as ordering might not be given
return srv_reply_fmt(client, REPLYFMT(ERR_NOTREGISTERED), "nickserv protected oper");
}
// normal operation commands
char* s;
std::string ss;
Channel* channel;
switch (cmd->type->t) {
case CMD_NICK:
assert(client->irc_state->nick); // already registered
ss.assign(client->irc_state->nick);
if (!client->irc_state->nick_set(cmd->params[0])) {
return srv_reply_fmt(client, REPLYFMT(ERR_NICKNAMEINUSE), cmd->params[0]);
} else {
LOG("NICK %s -> %s", ss.c_str(), client->irc_state->nick);
return Channel::nickchange(ss.c_str(), client); // announce nick change in channels
}
case CMD_JOIN: // TODO: support ,-separated list also at part, privmsg; there, support "0" for part'ing all
s = strtok(cmd->params[0], ",");
if (!s) {
return srv_reply_fmt(client, REPLYFMT(ERR_NEEDMOREPARAMS), cmd->type->s);
}
while (s) {
channel = Channel::getInst(s, !client->irc_state->is_oper || atoi(config.oper_channels.str) != 1);
if (!channel) {
if (!srv_reply_fmt(client, REPLYFMT(ERR_NOSUCHCHANNEL), cmd->params[0])) return false;
} else {
if (!channel->join(client)) {
channel->cleanup();
return false; // channel replies itself
} else {
LOG("JOIN %s", channel->name);
}
channel->cleanup(); // in case joining failed
}
s = strtok(NULL, ",");
}
return true;
case CMD_PART:
channel = Channel::getInst(cmd->params[0], true);
if (!channel) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOSUCHCHANNEL), cmd->params[0]);
}
LOG("PART %s", channel->name);
return channel->part(client, (cmd->nparams>1)? cmd->params[1]: NULL);
case CMD_WHO:
channel = Channel::getInst(cmd->params[0], true);
if (!channel) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOSUCHCHANNEL), cmd->params[0]);
}
return channel->who(client);
case CMD_MODE:
channel = Channel::getInst(cmd->params[0], true);
if (!channel) { // own user modes above
return srv_reply_fmt(client, REPLYFMT(ERR_NOSUCHCHANNEL), cmd->params[0]);
}
return channel->mode_get(client);
case CMD_TOPIC:
channel = Channel::getInst(cmd->params[0], true);
if (!channel) {
return srv_reply_fmt(client, REPLYFMT(ERR_NOSUCHCHANNEL), cmd->params[0]);
}
if (cmd->nparams == 1) {
return channel->topic(client);
} else {
return channel->topic(client, cmd->params[1]);
}
case CMD_PRIVMSG:
channel = Channel::getInst(cmd->params[0], true);
if (channel) {
return channel->privmsg(client, cmd->params[1]);
} else {
Client* c = ClientState::nick_find(cmd->params[0]);
if (c && Channel::is_peer(client, c)) { // don't allow non-peers, could be e.g. unidentified - how's the nick's presence known anyways?
(void)raw_reply_fmt_long(c, cmd->params[1], ":%s PRIVMSG %s",
client->irc_state->prefix_get(true, client == c || c->irc_state->is_oper || strncmp("\x01""DCC ", cmd->params[1], 5) == 0),
cmd->params[0]
);
if (c->irc_state->away) {
return srv_reply_fmt(client, REPLYFMT(RPL_AWAY), c->irc_state->nick, c->irc_state->away);
}
return true;
} else {
return srv_reply_fmt(client, REPLYFMT(ERR_NOSUCHNICK), cmd->params[0]);
}
}
default:
break;
}
assert(false);
return false;
}