aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/kike.c99
1 files changed, 99 insertions, 0 deletions
diff --git a/src/kike.c b/src/kike.c
index 82b3385..a4be70f 100644
--- a/src/kike.c
+++ b/src/kike.c
@@ -29,6 +29,7 @@
static struct config_item g_config_table[] =
{
{ "server_name", NULL, "Server name" },
+ { "server_info", "My server", "Brief server description" },
{ "motd", NULL, "MOTD filename" },
{ "catalog", NULL, "catgets localization catalog" },
@@ -259,6 +260,7 @@ struct client
unsigned mode; ///< User's mode
char *away_message; ///< Away message
+ time_t last_active; ///< Last PRIVMSG, to get idle time
};
static void
@@ -811,7 +813,13 @@ enum
IRC_RPL_ISON = 303,
IRC_RPL_UNAWAY = 305,
IRC_RPL_NOWAWAY = 306,
+ IRC_RPL_WHOISUSER = 311,
+ IRC_RPL_WHOISSERVER = 312,
+ IRC_RPL_WHOISOPERATOR = 313,
IRC_RPL_ENDOFWHO = 315,
+ IRC_RPL_WHOISIDLE = 317,
+ IRC_RPL_ENDOFWHOIS = 318,
+ IRC_RPL_WHOISCHANNELS = 319,
IRC_RPL_LIST = 322,
IRC_RPL_LISTEND = 323,
IRC_RPL_CHANNELMODEIS = 324,
@@ -880,7 +888,13 @@ static const char *g_default_replies[] =
[IRC_RPL_ISON] = ":%s",
[IRC_RPL_UNAWAY] = ":You are no longer marked as being away",
[IRC_RPL_NOWAWAY] = ":You have been marked as being away",
+ [IRC_RPL_WHOISUSER] = "%s %s %s * :%s",
+ [IRC_RPL_WHOISSERVER] = "%s %s :%s",
+ [IRC_RPL_WHOISOPERATOR] = "%s :is an IRC operator",
[IRC_RPL_ENDOFWHO] = "%s :End of WHO list",
+ [IRC_RPL_WHOISIDLE] = "%s %d :seconds idle",
+ [IRC_RPL_ENDOFWHOIS] = "%s :End of WHOIS list",
+ [IRC_RPL_WHOISCHANNELS] = "%s :%s",
[IRC_RPL_LIST] = "%s %d :%s",
[IRC_RPL_LISTEND] = ":End of LIST",
[IRC_RPL_CHANNELMODEIS] = "%s +%s",
@@ -1347,6 +1361,8 @@ static void
irc_handle_privmsg (const struct irc_message *msg, struct client *c)
{
irc_handle_user_message (msg, c, "PRIVMSG", true);
+ // Let's not care too much about success or failure
+ c->last_active = time (NULL);
}
static void
@@ -1579,6 +1595,87 @@ irc_handle_who (const struct irc_message *msg, struct client *c)
}
static void
+irc_send_whois_reply (struct client *c, const struct client *target)
+{
+ const char *nick = target->nickname;
+ irc_send_reply (c, IRC_RPL_WHOISUSER, nick, target->username,
+ target->hostname, target->realname);
+ irc_send_reply (c, IRC_RPL_WHOISSERVER, nick, target->ctx->server_name,
+ str_map_find (&c->ctx->config, "server_info"));
+ if (target->mode & IRC_USER_MODE_OPERATOR)
+ irc_send_reply (c, IRC_RPL_WHOISOPERATOR, nick);
+ irc_send_reply (c, IRC_RPL_WHOISIDLE, nick,
+ (int) (time (NULL) - target->last_active));
+
+ struct str_map_iter iter;
+ str_map_iter_init (&iter, &c->ctx->channels);
+ struct channel *chan;
+ struct channel_user *channel_user;
+ while ((chan = str_map_iter_next (&iter)))
+ if ((channel_user = channel_get_user (chan, target))
+ && (!(chan->modes & (IRC_CHAN_MODE_PRIVATE | IRC_CHAN_MODE_SECRET))
+ || channel_get_user (chan, c)))
+ {
+ struct str item;
+ str_init (&item);
+ if (channel_user->modes & IRC_CHAN_MODE_OPERATOR)
+ str_append_c (&item, '@');
+ else if (channel_user->modes & IRC_CHAN_MODE_VOICE)
+ str_append_c (&item, '+');
+ str_append (&item, chan->name);
+ str_append_c (&item, ' ');
+
+ // TODO: try to merge the results into as few messages as possible
+ irc_send_reply (c, IRC_RPL_WHOISCHANNELS, nick, item.str);
+
+ str_free (&item);
+ }
+
+ irc_send_reply (c, IRC_RPL_ENDOFWHOIS, nick);
+}
+
+static void
+irc_handle_whois (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len < 1)
+ RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
+ if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[0]))
+ RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]);
+
+ struct str_vector masks;
+ str_vector_init (&masks);
+ const char *masks_str = msg->params.vector[msg->params.len > 1];
+ split_str_ignore_empty (masks_str, ',', &masks);
+ for (size_t i = 0; i < masks.len; i++)
+ {
+ const char *mask = masks.vector[i];
+ struct client *target;
+ if (!strpbrk (mask, "*?"))
+ {
+ if (!(target = str_map_find (&c->ctx->users, mask)))
+ irc_send_reply (c, IRC_ERR_NOSUCHNICK, mask);
+ else
+ irc_send_whois_reply (c, target);
+ }
+ else
+ {
+ struct str_map_iter iter;
+ str_map_iter_init (&iter, &c->ctx->users);
+ bool found = false;
+ while ((target = str_map_iter_next (&iter))
+ && !irc_fnmatch (mask, target->nickname))
+ {
+ irc_send_whois_reply (c, target);
+ found = true;
+ }
+ if (!found)
+ irc_send_reply (c, IRC_ERR_NOSUCHNICK, mask);
+ }
+ }
+ str_vector_free (&masks);
+}
+
+static void
irc_send_rpl_topic (struct client *c, struct channel *chan)
{
if (!*chan->topic)
@@ -1901,6 +1998,7 @@ irc_register_handlers (struct server_context *ctx)
{ "LIST", true, irc_handle_list },
{ "NAMES", true, irc_handle_names },
{ "WHO", true, irc_handle_who },
+ { "WHOIS", true, irc_handle_whois },
{ "ISON", true, irc_handle_ison },
};
@@ -2239,6 +2337,7 @@ on_irc_client_available (const struct pollfd *pfd, void *user_data)
c->ctx = ctx;
c->socket_fd = fd;
c->hostname = xstrdup (host);
+ c->last_active = time (NULL);
LIST_PREPEND (ctx->clients, c);
ctx->n_clients++;