aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2014-08-04 00:35:01 +0200
committerPřemysl Janouch <p.janouch@gmail.com>2014-08-04 01:24:10 +0200
commit9bfdc741fefe31d18abe56b96382baab7a199c85 (patch)
treedef0120c6cf517a1c3a892aea777b0aa802121a5
parent3291fd5c7ad82852ed1db1350755f85cc412ab39 (diff)
downloadxK-9bfdc741fefe31d18abe56b96382baab7a199c85.tar.gz
xK-9bfdc741fefe31d18abe56b96382baab7a199c85.tar.xz
xK-9bfdc741fefe31d18abe56b96382baab7a199c85.zip
kike: stuff
Implemented a lot of the channel stuff and other stuff as well.
-rw-r--r--src/common.c29
-rw-r--r--src/kike.c784
2 files changed, 710 insertions, 103 deletions
diff --git a/src/common.c b/src/common.c
index 5a505b2..389120c 100644
--- a/src/common.c
+++ b/src/common.c
@@ -642,7 +642,7 @@ str_map_iter_next (struct str_map_iter *self)
self->link = self->link->next;
while (!self->link)
{
- if (self->next_index >= map->len)
+ if (self->next_index >= map->alloc)
return NULL;
self->link = map->map[self->next_index++];
}
@@ -1376,6 +1376,33 @@ strip_str_in_place (char *s, const char *stripped_chars)
return s;
}
+static char *
+join_str_vector (const struct str_vector *v, char delimiter)
+{
+ if (!v->len)
+ return xstrdup ("");
+
+ struct str result;
+ str_init (&result);
+ str_append (&result, v->vector[0]);
+ for (size_t i = 1; i < v->len; i++)
+ str_append_printf (&result, "%c%s", delimiter, v->vector[i]);
+ return str_steal (&result);
+}
+
+ATTRIBUTE_PRINTF (1, 2)
+static char *
+xstrdup_printf (const char *format, ...)
+{
+ va_list ap;
+ struct str tmp;
+ str_init (&tmp);
+ va_start (ap, format);
+ str_append_vprintf (&tmp, format, ap);
+ va_end (ap);
+ return str_steal (&tmp);
+}
+
static bool
str_append_env_path (struct str *output, const char *var, bool only_absolute)
{
diff --git a/src/kike.c b/src/kike.c
index e14a646..cda6984 100644
--- a/src/kike.c
+++ b/src/kike.c
@@ -103,6 +103,7 @@ enum validation_result
// Everything as per RFC 2812
#define IRC_MAX_NICKNAME 9
#define IRC_MAX_HOSTNAME 63
+#define IRC_MAX_CHANNEL_NAME 50
#define IRC_MAX_MESSAGE_LENGTH 510
static bool
@@ -194,6 +195,25 @@ irc_validate_nickname (const char *nickname)
return VALIDATION_OK;
}
+static enum validation_result
+irc_validate_channel_name (const char *channel_name)
+{
+ if (!*channel_name)
+ return VALIDATION_ERROR_EMPTY;
+ if (*channel_name != '#' || strpbrk (channel_name, "\7\r\n ,:"))
+ return VALIDATION_ERROR_INVALID;
+ if (strlen (channel_name) > IRC_MAX_CHANNEL_NAME)
+ return VALIDATION_ERROR_TOO_LONG;
+ return VALIDATION_OK;
+}
+
+static bool
+irc_is_valid_key (const char *key)
+{
+ // XXX: should be 7-bit as well but whatever
+ return irc_regex_match ("^[^\r\n\f\t\v ]{1,23}$", key);
+}
+
#undef SN
#undef N4
#undef N6
@@ -284,12 +304,7 @@ client_get_mode (struct client *self)
if (m & IRC_USER_MODE_RESTRICTED) str_append_c (&mode, 'r');
if (m & IRC_USER_MODE_OPERATOR) str_append_c (&mode, 'o');
if (m & IRC_USER_MODE_RX_SERVER_NOTICES) str_append_c (&mode, 's');
-
- if (mode.len)
- return str_steal (&mode);
-
- str_free (&mode);
- return NULL;
+ return str_steal (&mode);
}
#define IRC_SUPPORTED_CHAN_MODES "ov" "imnqpst" "kl"
@@ -339,6 +354,9 @@ channel_init (struct channel *self)
{
memset (self, 0, sizeof *self);
+ self->user_limit = -1;
+ self->topic = xstrdup ("");
+
str_vector_init (&self->ban_list);
str_vector_init (&self->exception_list);
str_vector_init (&self->invite_list);
@@ -363,6 +381,33 @@ channel_free (struct channel *self)
str_vector_free (&self->invite_list);
}
+static char *
+channel_get_mode (struct channel *self, bool disclose_secrets)
+{
+ struct str mode;
+ str_init (&mode);
+
+ unsigned m = self->modes;
+ if (m & IRC_CHAN_MODE_INVITE_ONLY) str_append_c (&mode, 'i');
+ if (m & IRC_CHAN_MODE_MODERATED) str_append_c (&mode, 'm');
+ if (m & IRC_CHAN_MODE_NO_OUTSIDE_MSGS) str_append_c (&mode, 'n');
+ if (m & IRC_CHAN_MODE_QUIET) str_append_c (&mode, 'q');
+ if (m & IRC_CHAN_MODE_PRIVATE) str_append_c (&mode, 'p');
+ if (m & IRC_CHAN_MODE_SECRET) str_append_c (&mode, 's');
+ if (m & IRC_CHAN_MODE_PROTECTED_TOPIC) str_append_c (&mode, 't');
+
+ if (self->user_limit != -1) str_append_c (&mode, 'l');
+ if (self->key) str_append_c (&mode, 'k');
+
+ if (self->user_limit != -1)
+ str_append_printf (&mode, " %ld", self->user_limit);
+
+ // XXX: is this correct? Try it on an existing implementation.
+ if (self->key && disclose_secrets)
+ str_append_printf (&mode, " %s", self->key);
+ return str_steal (&mode);
+}
+
struct server_context
{
int listen_fd; ///< Listening socket FD
@@ -450,22 +495,7 @@ server_context_free (struct server_context *self)
static void client_cancel_timers (struct client *);
static void client_set_kill_timer (struct client *);
static void client_update_poller (struct client *, const struct pollfd *);
-
-static void
-client_unregister (struct client *c, const char *reason)
-{
- if (!c->registered)
- return;
-
- // TODO: multicast a `:prefix QUIT :reason' to all people present on all
- // channels we were on.
- // TODO: remove ourselves from the channels, ...
- str_map_set (&c->ctx->users, c->nickname, NULL);
- free (c->nickname);
- c->nickname = NULL;
-
- c->registered = false;
-}
+static void client_unregister (struct client *, const char *);
static void
irc_try_finish_quit (struct server_context *ctx)
@@ -500,6 +530,8 @@ client_kill (struct client *c, const char *reason)
static void
irc_send_str (struct client *c, const struct str *s)
{
+ hard_assert (c->initialized && !c->closing_link);
+
// TODO: kill the connection above some "SendQ" threshold (careful!)
str_append_data (&c->write_buffer, s->str,
s->len > IRC_MAX_MESSAGE_LENGTH ? IRC_MAX_MESSAGE_LENGTH : s->len);
@@ -591,12 +623,10 @@ static void
on_irc_client_timeout_timer (void *user_data)
{
struct client *c = user_data;
- struct str reason;
- str_init (&reason);
- str_append_printf (&reason, "Ping timeout: >%u seconds",
- c->ctx->ping_interval);
- irc_close_link (c, reason.str);
- str_free (&reason);
+ char *reason = xstrdup_printf
+ ("Ping timeout: >%u seconds", c->ctx->ping_interval);
+ irc_close_link (c, reason);
+ free (reason);
}
static void
@@ -623,16 +653,31 @@ enum
IRC_RPL_CREATED = 3,
IRC_RPL_MYINFO = 4,
+ IRC_RPL_UMODEIS = 221,
IRC_RPL_LUSERCLIENT = 251,
IRC_RPL_LUSEROP = 252,
IRC_RPL_LUSERUNKNOWN = 253,
IRC_RPL_LUSERCHANNELS = 254,
IRC_RPL_LUSERME = 255,
+ IRC_RPL_AWAY = 301,
IRC_RPL_USERHOST = 302,
+ IRC_RPL_UNAWAY = 305,
+ IRC_RPL_NOWAWAY = 306,
IRC_RPL_LIST = 322,
IRC_RPL_LISTEND = 323,
+ IRC_RPL_CHANNELMODEIS = 324,
+ IRC_RPL_NOTOPIC = 331,
+ IRC_RPL_TOPIC = 332,
+ IRC_RPL_INVITELIST = 346,
+ IRC_RPL_ENDOFINVITELIST = 347,
+ IRC_RPL_EXCEPTLIST = 348,
+ IRC_RPL_ENDOFEXCEPTLIST = 349,
IRC_RPL_VERSION = 351,
+ IRC_RPL_NAMREPLY = 353,
+ IRC_RPL_ENDOFNAMES = 366,
+ IRC_RPL_BANLIST = 367,
+ IRC_RPL_ENDOFBANLIST = 368,
IRC_RPL_MOTD = 372,
IRC_RPL_MOTDSTART = 375,
IRC_RPL_ENDOFMOTD = 376,
@@ -640,6 +685,8 @@ enum
IRC_ERR_NOSUCHNICK = 401,
IRC_ERR_NOSUCHSERVER = 402,
+ IRC_ERR_NOSUCHCHANNEL = 403,
+ IRC_ERR_CANNOTSENDTOCHAN = 404,
IRC_ERR_NOORIGIN = 409,
IRC_ERR_NORECIPIENT = 411,
IRC_ERR_NOTEXTTOSEND = 412,
@@ -648,9 +695,20 @@ enum
IRC_ERR_NONICKNAMEGIVEN = 431,
IRC_ERR_ERRONEOUSNICKNAME = 432,
IRC_ERR_NICKNAMEINUSE = 433,
+ IRC_ERR_NOTONCHANNEL = 442,
+ IRC_ERR_SUMMONDISABLED = 445,
+ IRC_ERR_USERSDISABLED = 446,
IRC_ERR_NOTREGISTERED = 451,
IRC_ERR_NEEDMOREPARAMS = 461,
- IRC_ERR_ALREADYREGISTERED = 462
+ IRC_ERR_ALREADYREGISTERED = 462,
+ IRC_ERR_CHANNELISFULL = 471,
+ IRC_ERR_INVITEONLYCHAN = 473,
+ IRC_ERR_BANNEDFROMCHAN = 474,
+ IRC_ERR_BADCHANNELKEY = 475,
+ IRC_ERR_BADCHANMASK = 476,
+ IRC_ERR_CHANOPRIVSNEEDED = 482,
+
+ IRC_ERR_USERSDONTMATCH = 502
};
static const char *g_default_replies[] =
@@ -660,16 +718,31 @@ static const char *g_default_replies[] =
[IRC_RPL_CREATED] = ":This server was created %s",
[IRC_RPL_MYINFO] = "%s %s %s %s",
+ [IRC_RPL_UMODEIS] = "+%s",
[IRC_RPL_LUSERCLIENT] = ":There are %d users and %d services on %d servers",
[IRC_RPL_LUSEROP] = "%d :operator(s) online",
[IRC_RPL_LUSERUNKNOWN] = "%d :unknown connection(s)",
[IRC_RPL_LUSERCHANNELS] = "%d :channels formed",
[IRC_RPL_LUSERME] = ":I have %d clients and %d servers",
+ [IRC_RPL_AWAY] = "%s :%s",
[IRC_RPL_USERHOST] = ":%s",
+ [IRC_RPL_UNAWAY] = ":You are no longer marked as being away",
+ [IRC_RPL_NOWAWAY] = ":You have been marked as being away",
[IRC_RPL_LIST] = "%s %d :%s",
[IRC_RPL_LISTEND] = ":End of LIST",
+ [IRC_RPL_CHANNELMODEIS] = "%s +%s",
+ [IRC_RPL_NOTOPIC] = "%s :No topic is set",
+ [IRC_RPL_TOPIC] = "%s :%s",
+ [IRC_RPL_INVITELIST] = "%s %s",
+ [IRC_RPL_ENDOFINVITELIST] = "%s :End of channel invite list",
+ [IRC_RPL_EXCEPTLIST] = "%s %s",
+ [IRC_RPL_ENDOFEXCEPTLIST] = "%s :End of channel exception list",
[IRC_RPL_VERSION] = "%s.%d %s :%s",
+ [IRC_RPL_NAMREPLY] = "%c %s :%s",
+ [IRC_RPL_ENDOFNAMES] = "%s :End of NAMES list",
+ [IRC_RPL_BANLIST] = "%s %s",
+ [IRC_RPL_ENDOFBANLIST] = "%s :End of channel ban list",
[IRC_RPL_MOTD] = ":- %s",
[IRC_RPL_MOTDSTART] = ":- %s Message of the day - ",
[IRC_RPL_ENDOFMOTD] = ":End of MOTD command",
@@ -677,6 +750,8 @@ static const char *g_default_replies[] =
[IRC_ERR_NOSUCHNICK] = "%s :No such nick/channel",
[IRC_ERR_NOSUCHSERVER] = "%s :No such server",
+ [IRC_ERR_NOSUCHCHANNEL] = "%s :No such channel",
+ [IRC_ERR_CANNOTSENDTOCHAN] = "%s :Cannot send to channel",
[IRC_ERR_NOORIGIN] = ":No origin specified",
[IRC_ERR_NORECIPIENT] = ":No recipient given (%s)",
[IRC_ERR_NOTEXTTOSEND] = ":No text to send",
@@ -685,9 +760,20 @@ static const char *g_default_replies[] =
[IRC_ERR_NONICKNAMEGIVEN] = ":No nickname given",
[IRC_ERR_ERRONEOUSNICKNAME] = "%s :Erroneous nickname",
[IRC_ERR_NICKNAMEINUSE] = "%s :Nickname is already in use",
+ [IRC_ERR_NOTONCHANNEL] = "%s :You're not on that channel",
+ [IRC_ERR_SUMMONDISABLED] = ":SUMMON has been disabled",
+ [IRC_ERR_USERSDISABLED] = ":USERS has been disabled",
[IRC_ERR_NOTREGISTERED] = ":You have not registered",
[IRC_ERR_NEEDMOREPARAMS] = "%s :Not enough parameters",
[IRC_ERR_ALREADYREGISTERED] = ":Unauthorized command (already registered)",
+ [IRC_ERR_CHANNELISFULL] = "%s :Cannot join channel (+l)",
+ [IRC_ERR_INVITEONLYCHAN] = "%s :Cannot join channel (+i)",
+ [IRC_ERR_BANNEDFROMCHAN] = "%s :Cannot join channel (+b)",
+ [IRC_ERR_BADCHANNELKEY] = "%s :Cannot join channel (+k)",
+ [IRC_ERR_BADCHANMASK] = "%s :Bad Channel Mask",
+ [IRC_ERR_CHANOPRIVSNEEDED] = "%s :You're not channel operator",
+
+ [IRC_ERR_USERSDONTMATCH] = ":Cannot change mode for other users",
};
// XXX: this way we cannot typecheck the arguments, so we must be careful
@@ -709,15 +795,18 @@ irc_send_reply (struct client *c, int id, ...)
str_free (&tmp);
}
+#define RETURN_WITH_REPLY(c, ...) \
+ BLOCK_START \
+ irc_send_reply ((c), __VA_ARGS__); \
+ return; \
+ BLOCK_END
+
static void
irc_send_motd (struct client *c)
{
struct server_context *ctx = c->ctx;
if (!ctx->motd.len)
- {
- irc_send_reply (c, IRC_ERR_NOMOTD);
- return;
- }
+ RETURN_WITH_REPLY (c, IRC_ERR_NOMOTD);
irc_send_reply (c, IRC_RPL_MOTDSTART, ctx->server_name);
for (size_t i = 0; i < ctx->motd.len; i++)
@@ -725,13 +814,130 @@ irc_send_motd (struct client *c)
irc_send_reply (c, IRC_RPL_ENDOFMOTD);
}
-static bool
-client_on_channel (struct client *c, const struct channel *chan)
+static struct channel_user *
+channel_get_user (const struct channel *chan, const struct client *c)
{
for (struct channel_user *iter = chan->users; iter; iter = iter->next)
if (!irc_strcmp (iter->nickname, c->nickname))
- return true;
- return false;
+ return iter;
+ return NULL;
+}
+
+static struct channel_user *
+channel_add_user (struct channel *chan, const struct client *c)
+{
+ size_t nick_len = strlen (c->nickname);
+ struct channel_user *link = xcalloc (1, sizeof *link + nick_len + 1);
+ memcpy (link->nickname, c->nickname, nick_len + 1);
+ LIST_PREPEND (chan->users, link);
+ return link;
+}
+
+static void
+channel_remove_user (struct channel *chan, struct channel_user *user)
+{
+ LIST_UNLINK (chan->users, user);
+ free (user);
+}
+
+static size_t
+channel_user_count (const struct channel *chan)
+{
+ size_t result = 0;
+ for (struct channel_user *iter = chan->users; iter; iter = iter->next)
+ result++;
+ return result;
+}
+
+static void
+channel_destroy_if_empty (struct server_context *ctx, struct channel *chan)
+{
+ if (!chan->users)
+ {
+ str_map_set (&ctx->channels, chan->name, NULL);
+ channel_free (chan);
+ free (chan);
+ }
+}
+
+static bool
+client_in_mask_list (const struct client *c, const struct str_vector *mask)
+{
+ struct str client;
+ str_init (&client);
+ str_append_printf (&client, "%s!%s@%s",
+ c->nickname, c->username, c->hostname);
+ irc_strxfrm (client.str, client.str, client.len);
+ bool result = false;
+ for (size_t i = 0; i < mask->len; i++)
+ // FIXME: irc_strxfrm() for the mask (save in canonical format?)
+ if (!fnmatch (client.str, mask->vector[i], 0))
+ {
+ result = true;
+ break;
+ }
+ str_free (&client);
+ return result;
+}
+
+static void
+client_send_to_roommates (struct client *c, const char *message)
+{
+ struct str_map targets;
+ str_map_init (&targets);
+ targets.key_xfrm = irc_strxfrm;
+
+ struct str_map_iter iter;
+ str_map_iter_init (&iter, &c->ctx->channels);
+ struct channel *chan;
+ while ((chan = str_map_iter_next (&iter)))
+ {
+ if (chan->modes & IRC_CHAN_MODE_QUIET
+ || !channel_get_user (chan, c))
+ continue;
+
+ // When we're unregistering, the str_map_find() will return zero,
+ // which will prevent sending the QUIT message to ourselves.
+ for (struct channel_user *iter = chan->users; iter; iter = iter->next)
+ str_map_set (&targets, iter->nickname,
+ str_map_find (&c->ctx->users, iter->nickname));
+ }
+
+ str_map_iter_init (&iter, &targets);
+ struct client *target;
+ while ((target = str_map_iter_next (&iter)))
+ irc_send (target, "%s", message);
+}
+
+static void
+client_unregister (struct client *c, const char *reason)
+{
+ if (!c->registered)
+ return;
+
+ // Make the user effectively non-existent
+ str_map_set (&c->ctx->users, c->nickname, NULL);
+
+ char *message = xstrdup_printf (":%s!%s@%s QUIT :%s",
+ c->nickname, c->username, c->hostname, reason);
+ client_send_to_roommates (c, message);
+ free (message);
+
+ struct str_map_iter iter;
+ str_map_iter_init (&iter, &c->ctx->channels);
+ struct channel *chan;
+ while ((chan = str_map_iter_next (&iter)))
+ {
+ struct channel_user *user;
+ if (!(user = channel_get_user (chan, c)))
+ continue;
+ channel_remove_user (chan, user);
+ channel_destroy_if_empty (c->ctx, chan);
+ }
+
+ free (c->nickname);
+ c->nickname = NULL;
+ c->registered = false;
}
static void
@@ -754,7 +960,7 @@ irc_send_lusers (struct client *c)
struct channel *chan;
while ((chan = str_map_iter_next (&iter)))
if (!(chan->modes & IRC_CHAN_MODE_SECRET)
- || client_on_channel (c, chan))
+ || channel_get_user (chan, c))
n_channels++;
irc_send_reply (c, IRC_RPL_LUSERCLIENT,
@@ -795,7 +1001,7 @@ irc_try_finish_registration (struct client *c)
irc_send_motd (c);
char *mode = client_get_mode (c);
- if (mode)
+ if (*mode)
irc_send (c, ":%s MODE %s :+%s", c->nickname, c->nickname, mode);
free (mode);
}
@@ -816,54 +1022,45 @@ irc_handle_nick (const struct irc_message *msg, struct client *c)
{
struct server_context *ctx = c->ctx;
- if (c->registered)
- {
- irc_send_reply (c, IRC_ERR_ALREADYREGISTERED);
- return;
- }
if (msg->params.len < 1)
- {
- irc_send_reply (c, IRC_ERR_NONICKNAMEGIVEN);
- return;
- }
+ RETURN_WITH_REPLY (c, IRC_ERR_NONICKNAMEGIVEN);
const char *nickname = msg->params.vector[0];
if (irc_validate_nickname (nickname) != VALIDATION_OK)
+ RETURN_WITH_REPLY (c, IRC_ERR_ERRONEOUSNICKNAME, nickname);
+
+ struct client *client = str_map_find (&ctx->users, nickname);
+ if (client && client != c)
+ RETURN_WITH_REPLY (c, IRC_ERR_NICKNAMEINUSE, nickname);
+
+ if (c->registered)
{
- irc_send_reply (c, IRC_ERR_ERRONEOUSNICKNAME, nickname);
- return;
+ char *message = xstrdup_printf (":%s!%s@%s NICK :%s",
+ c->nickname, c->username, c->hostname, nickname);
+ client_send_to_roommates (c, message);
+ free (message);
}
+
+ // Release the old nickname and allocate a new one
if (c->nickname)
{
str_map_set (&ctx->users, c->nickname, NULL);
free (c->nickname);
}
- if (str_map_find (&ctx->users, nickname))
- {
- irc_send_reply (c, IRC_ERR_NICKNAMEINUSE, nickname);
- return;
- }
-
- // Allocate the nickname
c->nickname = xstrdup (nickname);
str_map_set (&ctx->users, nickname, c);
- irc_try_finish_registration (c);
+ if (!c->registered)
+ irc_try_finish_registration (c);
}
static void
irc_handle_user (const struct irc_message *msg, struct client *c)
{
if (c->registered)
- {
- irc_send_reply (c, IRC_ERR_ALREADYREGISTERED);
- return;
- }
+ RETURN_WITH_REPLY (c, IRC_ERR_ALREADYREGISTERED);
if (msg->params.len < 4)
- {
- irc_send_reply (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
- return;
- }
+ RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
const char *username = msg->params.vector[0];
const char *mode = msg->params.vector[1];
@@ -892,10 +1089,7 @@ static void
irc_handle_userhost (const struct irc_message *msg, struct client *c)
{
if (msg->params.len < 1)
- {
- irc_send_reply (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
- return;
- }
+ RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
struct str reply;
str_init (&reply);
@@ -964,27 +1158,23 @@ irc_handle_pong (const struct irc_message *msg, struct client *c)
static void
irc_handle_quit (const struct irc_message *msg, struct client *c)
{
- struct str reason;
- str_init (&reason);
- str_append_printf (&reason, "Quit: %s",
+ char *reason = xstrdup_printf ("Quit: %s",
msg->params.len > 0 ? msg->params.vector[0] : c->nickname);
- irc_close_link (c, reason.str);
- str_free (&reason);
+ irc_close_link (c, reason);
+ free (reason);
}
static void
irc_handle_time (const struct irc_message *msg, struct client *c)
{
if (msg->params.len > 0 && !irc_is_this_me (c->ctx, msg->params.vector[0]))
- irc_send_reply (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]);
- else
- {
- char buf[32];
- time_t now = time (NULL);
- struct tm tm;
- strftime (buf, sizeof buf, "%a %b %d %Y %T", localtime_r (&now, &tm));
- irc_send_reply (c, IRC_RPL_TIME, c->ctx->server_name, buf);
- }
+ RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]);
+
+ char buf[32];
+ time_t now = time (NULL);
+ struct tm tm;
+ strftime (buf, sizeof buf, "%a %b %d %Y %T", localtime_r (&now, &tm));
+ irc_send_reply (c, IRC_RPL_TIME, c->ctx->server_name, buf);
}
static void
@@ -998,27 +1188,126 @@ irc_handle_version (const struct irc_message *msg, struct client *c)
}
static void
-irc_handle_privmsg (const struct irc_message *msg, struct client *c)
+irc_channel_multicast (struct channel *chan, const char *message)
{
- if (msg->params.len < 1)
- irc_send_reply (c, IRC_ERR_NORECIPIENT, msg->command);
- else if (msg->params.len < 2 || !*msg->params.vector[1])
- irc_send_reply (c, IRC_ERR_NOTEXTTOSEND);
+ for (struct channel_user *iter = chan->users; iter; iter = iter->next)
+ {
+ struct client *c = str_map_find (&chan->ctx->users, iter->nickname);
+ irc_send (c, "%s", message);
+ }
+}
+
+static void
+irc_send_channel_list (struct client *c, const char *channel_name,
+ const struct str_vector *list, int reply, int end_reply)
+{
+ for (size_t i = 0; i < list->len; i++)
+ irc_send_reply (c, reply, channel_name, list->vector[i]);
+ irc_send_reply (c, end_reply);
+}
+
+static bool
+irc_maybe_send_channel_list (struct client *c, struct channel *chan,
+ const char *mode_string)
+{
+ if (*mode_string == '+')
+ mode_string++;
+
+ if (!strcmp (mode_string, "b"))
+ irc_send_channel_list (c, chan->name, &chan->ban_list,
+ IRC_RPL_BANLIST, IRC_RPL_ENDOFBANLIST);
+ else if (!strcmp (mode_string, "e"))
+ irc_send_channel_list (c, chan->name, &chan->exception_list,
+ IRC_RPL_EXCEPTLIST, IRC_RPL_ENDOFEXCEPTLIST);
+ else if (!strcmp (mode_string, "I"))
+ irc_send_channel_list (c, chan->name, &chan->invite_list,
+ IRC_RPL_INVITELIST, IRC_RPL_ENDOFINVITELIST);
else
+ return false;
+ return true;
+}
+
+static void
+irc_handle_mode (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len < 1)
+ RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
+
+ const char *target = msg->params.vector[0];
+ struct client *client = str_map_find (&c->ctx->users, target);
+ if (client)
+ {
+ if (irc_strcmp (target, c->nickname))
+ RETURN_WITH_REPLY (c, IRC_ERR_USERSDONTMATCH);
+
+ char *mode = client_get_mode (client);
+ irc_send_reply (c, IRC_RPL_UMODEIS, mode);
+ free (mode);
+
+ // TODO: mode modification
+ return;
+ }
+
+ struct channel *chan = str_map_find (&c->ctx->channels, target);
+ if (chan)
{
- const char *target = msg->params.vector[0];
- const char *text = msg->params.vector[1];
- struct client *target_c = str_map_find (&c->ctx->users, target);
- if (!target_c)
+ if (msg->params.len < 2)
{
- irc_send_reply (c, IRC_ERR_NOSUCHNICK, target);
+ char *mode = channel_get_mode (chan, channel_get_user (chan, c));
+ irc_send_reply (c, IRC_RPL_CHANNELMODEIS, target, mode);
+ free (mode);
return;
}
+ if (msg->params.len < 3
+ && irc_maybe_send_channel_list (c, chan, msg->params.vector[1]))
+ return;
+
+ // TODO: mode modification
+ return;
+ }
+
+ irc_send_reply (c, IRC_ERR_NOSUCHNICK, target);
+}
+
+static void
+irc_handle_privmsg (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len < 1)
+ RETURN_WITH_REPLY (c, IRC_ERR_NORECIPIENT, msg->command);
+ if (msg->params.len < 2 || !*msg->params.vector[1])
+ RETURN_WITH_REPLY (c, IRC_ERR_NOTEXTTOSEND);
+
+ const char *target = msg->params.vector[0];
+ const char *text = msg->params.vector[1];
+ struct client *client = str_map_find (&c->ctx->users, target);
+ if (client)
+ {
+ irc_send (client, ":%s!%s@%s PRIVMSG %s :%s",
+ c->nickname, c->username, c->hostname, target, text);
+ return;
+ }
- // TODO: channels
- irc_send (target_c, ":%s!%s@%s PRIVMSG %s :%s",
+ struct channel *chan = str_map_find (&c->ctx->channels, target);
+ if (chan)
+ {
+ struct channel_user *user = channel_get_user (chan, c);
+ if ((chan->modes & IRC_CHAN_MODE_NO_OUTSIDE_MSGS) && !user)
+ RETURN_WITH_REPLY (c, IRC_ERR_CANNOTSENDTOCHAN, target);
+ if ((chan->modes & IRC_CHAN_MODE_MODERATED) && (!user ||
+ !(user->modes & (IRC_CHAN_MODE_VOICE | IRC_CHAN_MODE_OPERATOR))))
+ RETURN_WITH_REPLY (c, IRC_ERR_CANNOTSENDTOCHAN, target);
+ if (client_in_mask_list (c, &chan->ban_list)
+ && !client_in_mask_list (c, &chan->exception_list))
+ RETURN_WITH_REPLY (c, IRC_ERR_CANNOTSENDTOCHAN, target);
+
+ char *message = xstrdup_printf (":%s!%s@%s PRIVMSG %s :%s",
c->nickname, c->username, c->hostname, target, text);
+ irc_channel_multicast (chan, message);
+ free (message);
+ return;
}
+
+ irc_send_reply (c, IRC_ERR_NOSUCHNICK, target);
}
static void
@@ -1036,10 +1325,7 @@ static void
irc_handle_list (const struct irc_message *msg, struct client *c)
{
if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[1]))
- {
- irc_send_reply (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]);
- return;
- }
+ RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]);
struct channel *chan;
if (msg->params.len == 0)
@@ -1048,7 +1334,7 @@ irc_handle_list (const struct irc_message *msg, struct client *c)
str_map_iter_init (&iter, &c->ctx->channels);
while ((chan = str_map_iter_next (&iter)))
if (!(chan->modes & (IRC_CHAN_MODE_PRIVATE | IRC_CHAN_MODE_SECRET))
- || client_on_channel (c, chan))
+ || channel_get_user (chan, c))
irc_send_rpl_list (c, chan);
}
else
@@ -1059,13 +1345,298 @@ irc_handle_list (const struct irc_message *msg, struct client *c)
for (size_t i = 0; i < channels.len; i++)
if ((chan = str_map_find (&c->ctx->channels, channels.vector[i]))
&& (!(chan->modes & IRC_CHAN_MODE_SECRET)
- || client_on_channel (c, chan)))
+ || channel_get_user (chan, c)))
irc_send_rpl_list (c, chan);
str_vector_free (&channels);
}
irc_send_reply (c, IRC_RPL_LISTEND);
}
+static void
+irc_send_rpl_namreply (struct client *c, const struct channel *chan)
+{
+ struct str_vector nicks;
+ str_vector_init (&nicks);
+
+ char type = '=';
+ if (chan->modes & IRC_CHAN_MODE_SECRET)
+ type = '@';
+ else if (chan->modes & IRC_CHAN_MODE_PRIVATE)
+ type = '*';
+
+ bool on_channel = channel_get_user (chan, c);
+ for (struct channel_user *iter = chan->users; iter; iter = iter->next)
+ {
+ struct client *target = str_map_find (&c->ctx->users, iter->nickname);
+ if (!on_channel && (target->mode & IRC_USER_MODE_INVISIBLE))
+ continue;
+
+ struct str result;
+ str_init (&result);
+ if (iter->modes & IRC_CHAN_MODE_OPERATOR)
+ str_append_c (&result, '@');
+ else if (iter->modes & IRC_CHAN_MODE_VOICE)
+ str_append_c (&result, '+');
+ str_append (&result, target->nickname);
+ str_vector_add_owned (&nicks, str_steal (&result));
+ }
+
+ if (nicks.len)
+ {
+ char *reply = join_str_vector (&nicks, ' ');
+ irc_send_reply (c, IRC_RPL_NAMREPLY, type, chan->name, reply);
+ free (reply);
+ }
+ str_vector_free (&nicks);
+}
+
+static void
+irc_handle_names (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[1]))
+ RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]);
+
+ struct channel *chan;
+ if (msg->params.len == 0)
+ {
+ struct str_map_iter iter;
+ str_map_iter_init (&iter, &c->ctx->channels);
+ while ((chan = str_map_iter_next (&iter)))
+ if (!(chan->modes & (IRC_CHAN_MODE_PRIVATE | IRC_CHAN_MODE_SECRET))
+ || channel_get_user (chan, c))
+ irc_send_rpl_namreply (c, chan);
+
+ // TODO
+ // If no <channel> parameter is given, a list of all channels and their
+ // occupants is returned. At the end of this list, a list of users who
+ // are visible but either not on any channel or not on a visible channel
+ // are listed as being on `channel' "*".
+ irc_send_reply (c, IRC_RPL_ENDOFNAMES, "*");
+ }
+ else
+ {
+ struct str_vector channels;
+ str_vector_init (&channels);
+ split_str_ignore_empty (msg->params.vector[0], ',', &channels);
+ for (size_t i = 0; i < channels.len; i++)
+ if ((chan = str_map_find (&c->ctx->channels, channels.vector[i]))
+ && (!(chan->modes & IRC_CHAN_MODE_SECRET)
+ || channel_get_user (chan, c)))
+ {
+ irc_send_rpl_namreply (c, chan);
+ irc_send_reply (c, IRC_RPL_ENDOFNAMES, channels.vector[i]);
+ }
+ str_vector_free (&channels);
+ }
+}
+
+static void
+irc_send_rpl_topic (struct client *c, struct channel *chan)
+{
+ if (!*chan->topic)
+ irc_send_reply (c, IRC_RPL_NOTOPIC, chan->name);
+ else
+ irc_send_reply (c, IRC_RPL_TOPIC, chan->name, chan->topic);
+}
+
+static void
+irc_handle_topic (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len < 1)
+ RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
+
+ const char *target = msg->params.vector[0];
+ struct channel *chan = str_map_find (&c->ctx->channels, target);
+ if (!chan)
+ RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHCHANNEL, target);
+
+ if (msg->params.len < 2)
+ {
+ irc_send_rpl_topic (c, chan);
+ return;
+ }
+
+ struct channel_user *user = channel_get_user (chan, c);
+ if (!user)
+ RETURN_WITH_REPLY (c, IRC_ERR_NOTONCHANNEL, target);
+
+ if ((chan->modes & IRC_CHAN_MODE_PROTECTED_TOPIC)
+ && !(user->modes & IRC_CHAN_MODE_OPERATOR))
+ RETURN_WITH_REPLY (c, IRC_ERR_CHANOPRIVSNEEDED);
+
+ free (chan->topic);
+ chan->topic = xstrdup (msg->params.vector[1]);
+
+ char *message = xstrdup_printf (":%s!%s@%s TOPIC %s :%s",
+ c->nickname, c->username, c->hostname, target, chan->topic);
+ irc_channel_multicast (chan, message);
+ free (message);
+}
+
+static void
+irc_try_part (struct client *c, const char *channel_name, const char *reason)
+{
+ if (!reason)
+ reason = c->nickname;
+
+ struct channel *chan;
+ if (!(chan = str_map_find (&c->ctx->channels, channel_name)))
+ RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHCHANNEL, channel_name);
+
+ struct channel_user *user;
+ if (!(user = channel_get_user (chan, c)))
+ RETURN_WITH_REPLY (c, IRC_ERR_NOTONCHANNEL, channel_name);
+
+ char *message = xstrdup_printf (":%s!%s@%s PART %s :%s",
+ c->nickname, c->username, c->hostname, channel_name, reason);
+ if (!(chan->modes & IRC_CHAN_MODE_QUIET))
+ irc_channel_multicast (chan, message);
+ else
+ irc_send (c, "%s", message);
+ free (message);
+
+ channel_remove_user (chan, user);
+ channel_destroy_if_empty (c->ctx, chan);
+}
+
+static void
+irc_part_all_channels (struct client *c)
+{
+ struct str_map_iter iter;
+ str_map_iter_init (&iter, &c->ctx->channels);
+ struct channel *chan = str_map_iter_next (&iter), *next;
+ while (chan)
+ {
+ // We have to be careful here, the channel might get destroyed
+ next = str_map_iter_next (&iter);
+ if (channel_get_user (chan, c))
+ irc_try_part (c, chan->name, NULL);
+ chan = next;
+ }
+}
+
+static void
+irc_handle_part (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len < 1)
+ RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
+
+ const char *reason = msg->params.len > 1 ? msg->params.vector[1] : NULL;
+ struct str_vector channels;
+ str_vector_init (&channels);
+ split_str_ignore_empty (msg->params.vector[0], ',', &channels);
+ for (size_t i = 0; i < channels.len; i++)
+ irc_try_part (c, channels.vector[i], reason);
+ str_vector_free (&channels);
+}
+
+static void
+irc_try_join (struct client *c, const char *channel_name, const char *key)
+{
+ struct channel *chan = str_map_find (&c->ctx->channels, channel_name);
+ unsigned user_mode = 0;
+ if (!chan)
+ {
+ if (irc_validate_channel_name (channel_name) != VALIDATION_OK)
+ RETURN_WITH_REPLY (c, IRC_ERR_BADCHANMASK, channel_name);
+ user_mode = IRC_CHAN_MODE_OPERATOR;
+
+ chan = xcalloc (1, sizeof *chan);
+ channel_init (chan);
+ str_map_set (&c->ctx->channels, channel_name, chan);
+
+ chan->ctx = c->ctx;
+ chan->name = xstrdup (channel_name);
+ }
+ else if (channel_get_user (chan, c))
+ return;
+
+ if ((chan->modes & IRC_CHAN_MODE_INVITE_ONLY)
+ && !client_in_mask_list (c, &chan->invite_list))
+ // TODO: exceptions caused by INVITE
+ RETURN_WITH_REPLY (c, IRC_ERR_INVITEONLYCHAN, channel_name);
+ if (chan->key && (!key || strcmp (key, chan->key)))
+ RETURN_WITH_REPLY (c, IRC_ERR_BADCHANNELKEY, channel_name);
+ if (chan->user_limit != -1
+ && channel_user_count (chan) >= (size_t) chan->user_limit)
+ RETURN_WITH_REPLY (c, IRC_ERR_CHANNELISFULL, channel_name);
+ if (client_in_mask_list (c, &chan->ban_list)
+ && !client_in_mask_list (c, &chan->exception_list))
+ RETURN_WITH_REPLY (c, IRC_ERR_BANNEDFROMCHAN, channel_name);
+
+ channel_add_user (chan, c)->modes = user_mode;
+
+ char *message = xstrdup_printf (":%s!%s@%s JOIN %s",
+ c->nickname, c->username, c->hostname, channel_name);
+ if (!(chan->modes & IRC_CHAN_MODE_QUIET))
+ irc_channel_multicast (chan, message);
+ else
+ irc_send (c, "%s", message);
+ free (message);
+
+ irc_send_rpl_topic (c, chan);
+ irc_send_rpl_namreply (c, chan);
+}
+
+static void
+irc_handle_join (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len < 1)
+ RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
+
+ if (!strcmp (msg->params.vector[0], "0"))
+ {
+ irc_part_all_channels (c);
+ return;
+ }
+
+ struct str_vector channels;
+ struct str_vector keys;
+ str_vector_init (&channels);
+ str_vector_init (&keys);
+ split_str_ignore_empty (msg->params.vector[0], ',', &channels);
+ if (msg->params.len > 1)
+ split_str_ignore_empty (msg->params.vector[1], ',', &keys);
+
+ for (size_t i = 0; i < channels.len; i++)
+ irc_try_join (c, channels.vector[i],
+ i < keys.len ? keys.vector[i] : NULL);
+
+ str_vector_free (&channels);
+ str_vector_free (&keys);
+}
+
+static void
+irc_handle_summon (const struct irc_message *msg, struct client *c)
+{
+ (void) msg;
+ irc_send_reply (c, IRC_ERR_SUMMONDISABLED);
+}
+
+static void
+irc_handle_users (const struct irc_message *msg, struct client *c)
+{
+ (void) msg;
+ irc_send_reply (c, IRC_ERR_USERSDISABLED);
+}
+
+static void
+irc_handle_away (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len < 1)
+ {
+ free (c->away_message);
+ c->away_message = NULL;
+ irc_send_reply (c, IRC_RPL_UNAWAY);
+ }
+ else
+ {
+ free (c->away_message);
+ c->away_message = xstrdup (msg->params.vector[0]);
+ irc_send_reply (c, IRC_RPL_NOWAWAY);
+ }
+}
+
// -----------------------------------------------------------------------------
struct irc_command
@@ -1079,6 +1650,7 @@ static void
irc_register_handlers (struct server_context *ctx)
{
// TODO: add an index for IRC_ERR_NOSUCHSERVER validation?
+ // TODO: add a minimal parameter count?
// TODO: more commands, see RFC 2812 :!
static const struct irc_command message_handlers[] =
{
@@ -1094,9 +1666,17 @@ irc_register_handlers (struct server_context *ctx)
{ "QUIT", false, irc_handle_quit },
{ "TIME", true, irc_handle_time },
{ "VERSION", true, irc_handle_version },
+ { "USERS", true, irc_handle_users },
+ { "SUMMON", true, irc_handle_summon },
+ { "AWAY", true, irc_handle_away },
+ { "MODE", true, irc_handle_mode },
{ "PRIVMSG", true, irc_handle_privmsg },
+ { "JOIN", true, irc_handle_join },
+ { "PART", true, irc_handle_part },
+ { "TOPIC", true, irc_handle_topic },
{ "LIST", true, irc_handle_list },
+ { "NAMES", true, irc_handle_names },
};
for (size_t i = 0; i < N_ELEMENTS (message_handlers); i++)