Mercurial > irccd
changeset 953:ab43ba409f9d
irccd: add SSL, and cleanup events
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 20 Jan 2021 12:32:59 +0100 |
parents | 2899474aefd7 |
children | 30643d18a635 |
files | irccd/main.c irccdctl/main.c lib/CMakeLists.txt lib/irccd/event.c lib/irccd/event.h lib/irccd/irccd.c lib/irccd/js-plugin.c lib/irccd/list.h lib/irccd/peer.c lib/irccd/peer.h lib/irccd/server.c lib/irccd/server.h lib/irccd/util.c lib/irccd/util.h tests/CMakeLists.txt tests/test-event.c |
diffstat | 16 files changed, 550 insertions(+), 333 deletions(-) [+] |
line wrap: on
line diff
--- a/irccd/main.c Sun Jan 17 19:05:39 2021 +0100 +++ b/irccd/main.c Wed Jan 20 12:32:59 2021 +0100 @@ -24,6 +24,8 @@ #include <irccd/server.h> #include <irccd/transport.h> #include <irccd/util.h> +#include <irccd/plugin.h> +#include <irccd/js-plugin.h> int main(int argc, char **argv) @@ -37,8 +39,9 @@ .nickname = "circ", .hostname = "malikania.fr", .port = 6697, - .flags = IRC_SERVER_FLAGS_SSL + .flags = IRC_SERVER_FLAGS_SSL | IRC_SERVER_FLAGS_JOIN_INVITE }; + struct irc_plugin p = {0}; irc_log_set_verbose(true); irc_bot_init(); @@ -46,5 +49,7 @@ irc_transport_bind("/tmp/irccd.sock"); irc_server_join(&s, "#test", NULL); irc_bot_add_server(irc_util_memdup(&s, sizeof (s))); + irc_js_plugin_open(&p, "/Users/markand/test.js"); + irc_bot_add_plugin(&p); irc_bot_run(); }
--- a/irccdctl/main.c Sun Jan 17 19:05:39 2021 +0100 +++ b/irccdctl/main.c Wed Jan 20 12:32:59 2021 +0100 @@ -235,6 +235,74 @@ ok(); } +static void +show_invite(char *line) +{ + const char *args[5] = {0}; + + if (irc_util_split(line, args, 5) == 5) { + printf("event: onInvite\n"); + printf("server: %s", args[1]); + printf("origin: %s", args[2]); + printf("channel: %s", args[3]); + printf("nickname: %s", args[4]); + } +} + +static void +show_message(char *line) +{ + const char *args[5] = {0}; + + if (irc_util_split(line, args, 5) == 5) { + printf("event: onMessage\n"); + printf("server: %s\n", args[1]); + printf("origin: %s\n", args[2]); + printf("channel: %s\n", args[3]); + printf("message: %s\n", args[4]); + } +} + +static const struct { + const char *event; + void (*show)(char *); +} watchtable[] = { + { "EVENT-INVITE", show_invite }, + { "EVENT-MESSAGE", show_message } +}; + +static void +show(char *ev) +{ + for (size_t i = 0; i < IRC_UTIL_SIZE(watchtable); ++i) { + if (strncmp(watchtable[i].event, ev, strlen(watchtable[i].event)) == 0) { + watchtable[i].show(ev); + break; + } + } +} + +static void +cmd_watch(int argc, char **argv) +{ + (void)argc; + (void)argv; + + struct timeval tv = {0}; + char *ev; + + /* Enable watch. */ + req("WATCH"); + ok(); + + /* Turn off timeout to receive indefinitely. */ + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)) < 0) + err(1, "setsockopt"); + + while ((ev = poll())) + show(ev); +} + static const struct cmd { const char *name; int minargs; @@ -250,7 +318,8 @@ { "server-nick", 2, 2, cmd_server_nick }, { "server-notice", 3, 3, cmd_server_notice }, { "server-part", 3, 3, cmd_server_part }, - { "server-topic", 3, 3, cmd_server_topic } + { "server-topic", 3, 3, cmd_server_topic }, + { "watch", 0, 0, cmd_watch } }; static int
--- a/lib/CMakeLists.txt Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/CMakeLists.txt Wed Jan 20 12:32:59 2021 +0100 @@ -26,6 +26,7 @@ irccd/config.h.in irccd/dl-plugin.c irccd/dl-plugin.h + irccd/event.c irccd/event.h irccd/irccd.c irccd/irccd.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/event.c Wed Jan 20 12:32:59 2021 +0100 @@ -0,0 +1,162 @@ +/* + * event.c -- IRC event + * + * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "event.h" +#include "server.h" + +static inline void +scan(char **line, char **str) +{ + char *p = strchr(*line, ' '); + + if (p) + *p = '\0'; + + *str = *line; + *line = p ? p + 1 : strchr(*line, '\0'); +} + +bool +irc_event_is_ctcp(const char *line) +{ + size_t length; + + if (!line) + return false; + if ((length = strlen(line)) < 2) + return false; + + return line[0] == 0x1 && line[length - 1] == 0x1; +} + +char * +irc_event_ctcp(char *line) +{ + /* Skip first \001. */ + if (*line == '\001') + line++; + + /* Remove last \001. */ + line[strcspn(line, "\001")] = '\0'; + + if (strncmp(line, "ACTION ", 7) == 0) + line += 7; + + return line; +} + +bool +irc_event_parse(struct irc_event_msg *ev, const char *line) +{ + assert(ev); + assert(line); + + char *ptr = ev->buf; + size_t a; + + memset(ev, 0, sizeof (*ev)); + strlcpy(ev->buf, line, sizeof (ev->buf)); + + /* + * IRC message is defined as following: + * + * [:prefix] command arg1 arg2 [:last-argument] + */ + if (*line == ':') + scan((++ptr, &ptr), &ev->prefix); /* prefix */ + + scan(&ptr, &ev->cmd); /* command */ + + /* And finally arguments. */ + for (a = 0; *ptr && a < IRC_ARGS_MAX; ++a) { + if (*ptr == ':') { + ev->args[a] = ptr + 1; + ptr = strchr(ptr, '\0'); + } else + scan(&ptr, &ev->args[a]); + } + + if (a >= IRC_ARGS_MAX) + return errno = EMSGSIZE, false; + if (ev->cmd == NULL) + return errno = EBADMSG, false; + + return true; +} + +bool +irc_event_str(const struct irc_event *ev, char *str, size_t strsz) +{ + assert(ev); + assert(str); + + int written = 0; + + switch (ev->type) { + case IRC_EVENT_INVITE: + written = snprintf(str, strsz, "EVENT-INVITE %s %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]); + break; + case IRC_EVENT_JOIN: + written = snprintf(str, strsz, "EVENT-JOIN %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0]); + break; + case IRC_EVENT_KICK: + written = snprintf(str, strsz, "EVENT-KICK %s %s %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0], ev->msg.args[1], ev->msg.args[2]); + break; + case IRC_EVENT_ME: + written = snprintf(str, strsz, "EVENT-ME %s %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]); + break; + case IRC_EVENT_MESSAGE: + written = snprintf(str, strsz, "EVENT-MESSAGE %s %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]); + break; + case IRC_EVENT_MODE: + written = snprintf(str, strsz, "EVENT-MODE %s %s %s %s %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0], ev->msg.args[1], ev->msg.args[2], + ev->msg.args[3], ev->msg.args[4]); + break; + case IRC_EVENT_NICK: + written = snprintf(str, strsz, "EVENT-NICK %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0]); + break; + case IRC_EVENT_NOTICE: + written = snprintf(str, strsz, "EVENT-NOTICE %s %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]); + break; + case IRC_EVENT_PART: + written = snprintf(str, strsz, "EVENT-PART %s %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]); + break; + case IRC_EVENT_TOPIC: + written = snprintf(str, strsz, "EVENT-TOPIC %s %s %s %s", ev->server->name, + ev->msg.prefix, ev->msg.args[0], ev->msg.args[1]); + break; + default: + break; + } + + return written > 0; +}
--- a/lib/irccd/event.h Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/event.h Wed Jan 20 12:32:59 2021 +0100 @@ -19,6 +19,7 @@ #ifndef IRCCD_EVENT_H #define IRCCD_EVENT_H +#include <stdbool.h> #include <stddef.h> #include "limits.h" @@ -43,95 +44,29 @@ IRC_EVENT_WHOIS }; +struct irc_event_msg { + char *prefix; + char *cmd; + char *args[IRC_ARGS_MAX]; + char buf[IRC_MESSAGE_MAX]; +}; + struct irc_event { enum irc_event_type type; struct irc_server *server; - - /* - * Raw arguments. - * [0]: prefix - * [1]: origin - * [2 to argsz]: arguments - */ - char args[IRC_ARGS_MAX][IRC_MESSAGE_MAX]; - size_t argsz; - - /* Conveniently organized union depending on the type. */ - union { - struct { - char *origin; - char *channel; - char *nickname; - } invite; - - struct { - char *origin; - char *channel; - } join; - - struct { - char *origin; - char *channel; - char *target; - char *reason; - } kick; - - struct { - char *origin; - char *channel; - char *message; - } me; - - struct { - char *origin; - char *channel; - char *message; - } message; - - struct { - char *origin; - char *channel; - char *mode; - char *limit; - char *user; - char *mask; - } mode; - - struct { - char *origin; - char *nickname; - } nick; - - struct { - struct irc_channel *channel; - } names; - - struct { - char *origin; - char *channel; - char *message; - } notice; - - struct { - char *origin; - char *channel; - char *reason; - } part; - - struct { - char *origin; - char *channel; - char *topic; - } topic; - - struct { - char *nick; - char *user; - char *hostname; - char *realname; - char **channels; - } whois; - }; + struct irc_event_msg msg; }; +bool +irc_event_is_ctcp(const char *); + +char * +irc_event_ctcp(char *); + +bool +irc_event_parse(struct irc_event_msg *, const char *); + +bool +irc_event_str(const struct irc_event *, char *, size_t); + #endif /* !IRCCD_EVENT_H */
--- a/lib/irccd/irccd.c Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/irccd.c Wed Jan 20 12:32:59 2021 +0100 @@ -90,7 +90,20 @@ return pkg; } -static void +static inline void +broadcast(const struct irc_event *ev) +{ + char buf[IRC_MESSAGE_MAX]; + + if (!irc_event_str(ev, buf, sizeof (buf))) + return; + + for (size_t i = 0; i < irc.peersz; ++i) + if (irc.peers[i].is_watching) + irc_peer_send(&irc.peers[i], buf); +} + +static inline void invoke(const struct irc_event *ev) { for (size_t i = 0; i < irc.pluginsz; ++i) @@ -118,7 +131,7 @@ struct irc_peer peer; struct irc_event ev; - if (poll(pkg->fds, pkg->fdsz, 1000) < 0) + if (poll(pkg->fds, pkg->fdsz, 1000) < 0 && errno != EINTR) err(1, "poll"); /* @@ -150,9 +163,12 @@ * For every server, poll any kind of new event and pass them to the * plugin unless the rules explicitly disallow us to do so. */ - IRC_LIST_FOREACH(irc.servers, s) - while (irc_server_poll(s, &ev)) + IRC_LIST_FOREACH(irc.servers, s) { + while (irc_server_poll(s, &ev)) { + broadcast(&ev); invoke(&ev); + } + } } static void
--- a/lib/irccd/js-plugin.c Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/js-plugin.c Wed Jan 20 12:32:59 2021 +0100 @@ -40,6 +40,7 @@ #include "jsapi-unicode.h" #include "log.h" #include "plugin.h" +#include "server.h" #include "util.h" struct self { @@ -84,8 +85,10 @@ } static void -push_names(duk_context *ctx, const struct irc_channel *ch) +push_names(duk_context *ctx, const struct irc_event *ev) { + const struct irc_channel *ch = irc_server_find(ev->server, ev->msg.args[0]); + duk_push_array(ctx); for (size_t i = 0; i < ch->usersz; ++i) { @@ -271,6 +274,9 @@ static void handle(struct irc_plugin *plg, const struct irc_event *ev) { + (void)plg; + (void)ev; + switch (ev->type) { case IRC_EVENT_CONNECT: call(plg, "onConnect", "S", ev->server); @@ -279,53 +285,52 @@ call(plg, "onDisconnect", "S", ev->server); break; case IRC_EVENT_INVITE: - call(plg, "onInvite", "Ssss", ev->server, ev->invite.origin, - ev->invite.channel, ev->invite.nickname); + call(plg, "onInvite", "Ss s", ev->server, ev->msg.prefix, + ev->msg.args[1]); break; case IRC_EVENT_JOIN: - call(plg, "onJoin", "Sss", ev->server, ev->join.origin, - ev->join.channel); + call(plg, "onJoin", "Ss s", ev->server, ev->msg.prefix, + ev->msg.args[0]); break; case IRC_EVENT_KICK: - call(plg, "onKick", "Sssss", ev->server, ev->kick.origin, ev->kick.channel, - ev->kick.target, ev->kick.reason); + call(plg, "onKick", "Ss sss", ev->server, ev->msg.prefix, + ev->msg.args[0], ev->msg.args[1], ev->msg.args[2]); break; case IRC_EVENT_ME: - call(plg, "onMe", "Ssss", ev->server, ev->me.origin, ev->me.channel, - ev->me.message); + call(plg, "onMe", "Ss ss", ev->server, ev->msg.prefix, + ev->msg.args[0], ev->msg.args[1]); break; case IRC_EVENT_MESSAGE: - call(plg, "onMessage", "Ssss", ev->server, ev->message.origin, - ev->message.channel, ev->message.message); + call(plg, "onMessage", "Ss ss", ev->server, ev->msg.prefix, + ev->msg.args[0], ev->msg.args[1]); break; case IRC_EVENT_MODE: - call(plg, "onMode", "Sssssss", ev->server, ev->mode.origin, ev->mode.channel, - ev->mode.mode, ev->mode.limit, ev->mode.user, ev->mode.mask); + call(plg, "onMode", "Ss sss ss", ev->server, ev->msg.prefix, + ev->msg.args[0], ev->msg.args[1], ev->msg.args[2], + ev->msg.args[3], ev->msg.args[4]); break; case IRC_EVENT_NAMES: - call(plg, "onNames", "Ssx", ev->server, ev->names.channel->name, - push_names, ev->names.channel); + call(plg, "onNames", "Ss x", ev->server, ev->msg.args[1], + push_names); break; case IRC_EVENT_NICK: - call(plg, "onNick", "Sss", ev->server, ev->nick.origin, ev->nick.nickname); + call(plg, "onNick", "Ss s", ev->server, ev->msg.prefix, + ev->msg.args[0]); break; case IRC_EVENT_NOTICE: - call(plg, "onNotice", "Ssss", ev->server, ev->notice.origin, - ev->notice.channel, ev->notice.message); + call(plg, "onNotice", "Ss ss", ev->server, ev->msg.prefix, + ev->msg.args[0], ev->msg.args[1]); break; case IRC_EVENT_PART: - call(plg, "onPart", "Ssss", ev->server, ev->part.origin, ev->part.channel, - ev->part.reason); + call(plg, "onPart", "Ss ss", ev->server, ev->msg.prefix, + ev->msg.args[0], ev->msg.args[1]); break; case IRC_EVENT_TOPIC: - call(plg, "onTopic", "Ssss", ev->server, ev->topic.origin, - ev->topic.channel, ev->topic.topic); + call(plg, "onTopic", "Ss ss", ev->server, ev->msg.prefix, + ev->msg.args[0], ev->msg.args[1]); break; -#if 0 case IRC_EVENT_WHOIS: - call(plg, "onWhois", "Sx", ev->topic.server, js_event_whois_push, &ev->whois.whois); break; -#endif default: break; }
--- a/lib/irccd/list.h Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/list.h Wed Jan 20 12:32:59 2021 +0100 @@ -16,8 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef IRCD_LIST_H -#define IRCD_LIST_H +#ifndef IRCCD_LIST_H +#define IRCCD_LIST_H #define IRC_LIST_ADD(h, o) \ do { \ @@ -38,11 +38,10 @@ (o)->next = (o)->prev = NULL; \ } while (0) - #define IRC_LIST_FOREACH(h, s) \ - for ((s) = (h); (s); (s) = (s)->next) + for ((s) = (h); (s); (s) = (s)->next) #define IRC_LIST_FOREACH_SAFE(h, s, tmp) \ for ((s) = (h); s && ((tmp) = (s)->next, 1); (s) = (tmp)) -#endif /* IRC_LIST_H */ +#endif /* IRCCD_LIST_H */
--- a/lib/irccd/peer.c Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/peer.c Wed Jan 20 12:32:59 2021 +0100 @@ -298,6 +298,16 @@ return ok(p); } +static int +cmd_watch(struct irc_peer *p, char *line) +{ + (void)line; + + p->is_watching = true; + + return ok(p); +} + static const struct cmd { const char *name; int (*call)(struct irc_peer *, char *); @@ -313,6 +323,7 @@ { "SERVER-NOTICE", cmd_server_notice }, { "SERVER-PART", cmd_server_part }, { "SERVER-TOPIC", cmd_server_topic }, + { "WATCH", cmd_watch } }; static int
--- a/lib/irccd/peer.h Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/peer.h Wed Jan 20 12:32:59 2021 +0100 @@ -27,6 +27,7 @@ struct irc_peer { int fd; + bool is_watching; char in[IRC_BUF_MAX]; char out[IRC_BUF_MAX]; };
--- a/lib/irccd/server.c Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/server.c Wed Jan 20 12:32:59 2021 +0100 @@ -49,33 +49,6 @@ char host[IRC_HOST_MAX]; }; -static bool -is_ctcp(const char *line) -{ - assert(line); - - const size_t length = strlen(line); - - if (length < 2) - return false; - - return line[0] == 0x1 && line[length - 1] == 0x1; -} - -static char * -ctcp(char *line) -{ - assert(line); - - /* Remove last \001. */ - line[strcspn(line, "\001")] = 0; - - if (strncmp(line, "ACTION ", 7) == 0) - line += 7; - - return line; -} - static int cmp_channel(const struct irc_channel *c1, const struct irc_channel *c2) { @@ -117,16 +90,21 @@ static struct irc_channel * add_channel(struct irc_server *s, const char *name, const char *password, bool joined) { - struct irc_channel ch = { - .joined = joined - }; + struct irc_channel chnew = {0}, *ch; - strlcpy(ch.name, name, sizeof (ch.name)); + if ((ch = irc_server_find(s, name))) { + ch->joined = joined; + return ch; + } + + strlcpy(chnew.name, name, sizeof (chnew.name)); if (password) - strlcpy(ch.password, password, sizeof (ch.password)); + strlcpy(chnew.password, password, sizeof (chnew.password)); - IRC_SET_ALLOC_PUSH(&s->channels, &s->channelsz, &ch, cmp_channel); + chnew.joined = joined; + + IRC_SET_ALLOC_PUSH(&s->channels, &s->channelsz, &chnew, cmp_channel); return irc_server_find(s, name); } @@ -134,14 +112,15 @@ static void remove_channel(struct irc_server *s, struct irc_channel *ch) { + irc_channel_clear(ch); IRC_SET_ALLOC_REMOVE(&s->channels, &s->channelsz, ch); } static void read_support_prefix(struct irc_server *s, const char *value) { - char modes[16 + 1] = { 0 }; - char tokens[16 + 1] = { 0 }; + char modes[16 + 1] = {0}; + char tokens[16 + 1] = {0}; if (sscanf(value, "(%16[^)])%16s", modes, tokens) == 2) { char *pm = modes; @@ -161,26 +140,27 @@ } static void -convert_connect(struct irc_server *s, struct irc_event *ev) +handle_connect(struct irc_server *s, struct irc_event *ev) { s->state = IRC_SERVER_STATE_CONNECTED; - ev->type = IRC_EVENT_CONNECT; - ev->server = s; - /* Now join all channels that were requested. */ for (size_t i = 0; i < s->channelsz; ++i) irc_server_join(s, s->channels[i].name, s->channels[i].password); + + ev->type = IRC_EVENT_CONNECT; } static void -convert_support(struct irc_server *s, struct irc_event *ev) +handle_support(struct irc_server *s, struct irc_event *ev) { + (void)ev; + char key[64]; char value[64]; - for (size_t i = 4; i < ev->argsz; ++i) { - if (sscanf(ev->args[i], "%63[^=]=%63s", key, value) != 2) + for (size_t i = 2; ev->msg.args[i]; ++i) { + if (sscanf(ev->msg.args[i], "%63[^=]=%63s", key, value) != 2) continue; if (strcmp(key, "PREFIX") == 0) @@ -191,85 +171,68 @@ } static void -convert_join(struct irc_server *s, struct irc_event *ev) +handle_invite(struct irc_server *s, struct irc_event *ev) { - const struct origin *origin = parse_origin(ev->args[0]); - struct irc_channel *ch = NULL; + if (strcmp(ev->msg.args[0], s->nickname) == 0 && s->flags & IRC_SERVER_FLAGS_JOIN_INVITE) + irc_server_join(s, ev->msg.args[1], NULL); - ev->type = IRC_EVENT_JOIN; - ev->server = s; - ev->join.origin = ev->args[0]; - ev->join.channel = ev->args[2]; - - if (!(ch = irc_server_find(s, ev->args[2]))) - ch = add_channel(s, ev->args[2], NULL, true); - else - ch->joined = true; - - irc_channel_add(ch, origin->nickname, 0); + ev->type = IRC_EVENT_INVITE; } static void -convert_kick(struct irc_server *s, struct irc_event *ev) +handle_join(struct irc_server *s, struct irc_event *ev) { - struct irc_channel *ch = irc_server_find(s, ev->args[2]); + add_channel(s, ev->msg.args[0], NULL, true); - ev->type = IRC_EVENT_KICK; - ev->server = s; - ev->kick.origin = ev->args[0]; - ev->kick.channel = ev->args[2]; - ev->kick.target = ev->args[3]; - ev->kick.reason = ev->args[4]; + ev->type = IRC_EVENT_JOIN; +} + +static void +handle_kick(struct irc_server *s, struct irc_event *ev) +{ + struct irc_channel *ch = add_channel(s, ev->msg.args[0], NULL, true); /* * If the bot was kicked itself mark the channel as not joined and * rejoin it automatically if the option is set. */ - if (strcmp(ev->args[3], s->nickname) == 0 && ch) { + if (strcmp(ev->msg.args[1], s->nickname) == 0) { ch->joined = false; irc_channel_clear(ch); if (s->flags & IRC_SERVER_FLAGS_AUTO_REJOIN) irc_server_join(s, ch->name, ch->password); } else - irc_channel_remove(ch, ev->args[3]); + irc_channel_remove(ch, ev->msg.args[1]); + + ev->type = IRC_EVENT_KICK; } static void -convert_mode(struct irc_server *s, struct irc_event *ev) +handle_mode(struct irc_server *s, struct irc_event *ev) { (void)s; (void)ev; - for (size_t i = 0; i < ev->argsz; ++i) { - printf("MODE: %zu=%s\n", i, ev->args[i]); - } + ev->type = IRC_EVENT_MODE; } static void -convert_part(struct irc_server *s, struct irc_event *ev) +handle_part(struct irc_server *s, struct irc_event *ev) { - const struct origin *origin = parse_origin(ev->args[0]); - struct irc_channel *ch = irc_server_find(s, ev->args[2]); + const struct origin *origin = parse_origin(ev->msg.prefix); + struct irc_channel *ch = add_channel(s, ev->msg.args[0], NULL, true); + + if (strcmp(origin->nickname, s->nickname) == 0) + remove_channel(s, ch); ev->type = IRC_EVENT_PART; - ev->server = s; - ev->part.origin = ev->args[0]; - ev->part.channel = ev->args[2]; - ev->part.reason = ev->args[3]; - - if (ch && strcmp(origin->nickname, s->nickname) == 0) - remove_channel(s, ch); } static void -convert_msg(struct irc_server *s, struct irc_event *ev) +handle_msg(struct irc_server *s, struct irc_event *ev) { - ev->type = IRC_EVENT_MESSAGE; - ev->server = s; - ev->message.origin = ev->args[0]; - ev->message.channel = ev->args[2]; - ev->message.message = ev->args[3]; + (void)s; /* * Detect CTCP commands which are PRIVMSG with a special boundaries. @@ -277,164 +240,104 @@ * Example: * PRIVMSG jean :\001ACTION I'm eating\001. */ - if (is_ctcp(ev->args[3])) { + if (irc_event_is_ctcp(ev->msg.args[1])) { ev->type = IRC_EVENT_ME; - ev->message.message = ctcp(ev->args[3] + 1); - } + ev->msg.args[1] = irc_event_ctcp(ev->msg.args[1]); + } else + ev->type = IRC_EVENT_MESSAGE; } static void -convert_nick(struct irc_server *s, struct irc_event *ev) +handle_nick(struct irc_server *s, struct irc_event *ev) { - const struct origin *origin = parse_origin(ev->args[0]); + const struct origin *origin = parse_origin(ev->msg.prefix); /* Update nickname if it is myself. */ if (strcmp(origin->nickname, s->nickname) == 0) - strlcpy(s->nickname, ev->args[2], sizeof (s->nickname)); -} + strlcpy(s->nickname, ev->msg.args[0], sizeof (s->nickname)); -static void -convert_notice(struct irc_server *s, struct irc_event *ev) -{ - ev->type = IRC_EVENT_NOTICE; - ev->server = s; - ev->notice.origin = ev->args[0]; - ev->notice.channel = ev->args[2]; - ev->notice.message = ev->args[3]; + ev->type = IRC_EVENT_NICK; } static void -convert_topic(struct irc_server *s, struct irc_event *ev) +handle_notice(struct irc_server *s, struct irc_event *ev) { - ev->type = IRC_EVENT_TOPIC; - ev->server = s; - ev->topic.origin = ev->args[0]; - ev->topic.channel = ev->args[2]; - ev->topic.topic = ev->args[3]; -} + (void)s; -static void -convert_ping(struct irc_server *s, struct irc_event *ev) -{ - irc_server_send(s, "PONG %s", ev->args[0]); + ev->type = IRC_EVENT_NOTICE; } static void -convert_names(struct irc_server *s, struct irc_event *ev) +handle_topic(struct irc_server *s, struct irc_event *ev) { - struct irc_channel *ch; - char *p, *n; - - if (ev->argsz < 6) - return; - - /* TODO: create if not exist */ - ch = irc_server_find(s, ev->args[4]); + (void)s; - for (p = ev->args[5]; p; ) { - n = strchr(p, ' '); + ev->type = IRC_EVENT_TOPIC; +} - if (n) - *n = '\0'; - if (strlen(p) > 0) - add_nick(s, ch, p); - - p = n ? n + 1 : NULL; - } +static void +handle_ping(struct irc_server *s, struct irc_event *ev) +{ + irc_server_send(s, "PONG %s", ev->msg.args[0]); } static void -convert_endofnames(struct irc_server *s, struct irc_event *ev) +handle_names(struct irc_server *s, struct irc_event *ev) { - for (size_t i = 0; i < ev->argsz; ++i) - printf("%zd: %s\n", i, ev->args[i]); - ev->type = IRC_EVENT_NAMES; - ev->server = s; - ev->names.channel = irc_server_find(s, ev->args[3]); -} + struct irc_channel *ch; + char *p, *token; -static const struct convert { - const char *command; - void (*convert)(struct irc_server *, struct irc_event *); -} converters[] = { - /* Must be kept ordered. */ - { "001", convert_connect }, - { "005", convert_support }, - { "353", convert_names }, - { "366", convert_endofnames }, - { "JOIN", convert_join }, - { "KICK", convert_kick }, - { "MODE", convert_mode }, - { "NICK", convert_nick }, - { "NOTICE", convert_notice }, - { "PART", convert_part }, - { "PING", convert_ping }, - { "PRIVMSG", convert_msg }, - { "TOPIC", convert_topic } -}; + ch = add_channel(s, ev->msg.args[2], NULL, true); -static int -compare_converter(const void *d1, const void *d2) -{ - return strcmp(d1, ((const struct convert *)d2)->command); + /* TODO: libcompat for strtok_r. */ + for (p = ev->msg.args[3]; (token = strtok_r(p, " ", &p)); ) + if (strlen(token) > 0) + add_nick(s, ch, token); } static void -convert(struct irc_server *s, struct irc_event *ev) +handle_endofnames(struct irc_server *s, struct irc_event *ev) { - const struct convert *c = bsearch(ev->args[1], converters, IRC_UTIL_SIZE(converters), - sizeof (*c), &(compare_converter)); + (void)s; - if (c) - c->convert(s, ev); + ev->type = IRC_EVENT_NAMES; } -static inline bool -scan(struct irc_event *ev, const char **line) -{ - const char *p = *line; - size_t i = 0; - - /* Copy argument. */ - while (i < IRC_MESSAGE_MAX && *p && !isspace(*p)) - ev->args[ev->argsz][i++] = *p++; +static const struct handler { + const char *command; + void (*handle)(struct irc_server *, struct irc_event *); +} handlers[] = { + /* Must be kept ordered. */ + { "001", handle_connect }, + { "005", handle_support }, + { "353", handle_names }, + { "366", handle_endofnames }, + { "INVITE", handle_invite }, + { "JOIN", handle_join }, + { "KICK", handle_kick }, + { "MODE", handle_mode }, + { "NICK", handle_nick }, + { "NOTICE", handle_notice }, + { "PART", handle_part }, + { "PING", handle_ping }, + { "PRIVMSG", handle_msg }, + { "TOPIC", handle_topic } +}; - /* Skip optional spaces. */ - while (*p && isspace(*p)) - ++p; - - if (i >= IRC_MESSAGE_MAX) - return false; - - *line = p; - ev->argsz++; - - return true; +static int +compare_handler(const void *d1, const void *d2) +{ + return strcmp(d1, ((const struct handler *)d2)->command); } static void -parse(struct irc_server *s, struct irc_event *ev, const char *line) +handle(struct irc_server *s, struct irc_event *ev) { - if (!*line || *line++ != ':') - return; - if (!scan(ev, &line)) /* Prefix */ - return; - if (!scan(ev, &line)) /* Command */ - return; + const struct handler *c = bsearch(ev->msg.cmd, handlers, IRC_UTIL_SIZE(handlers), + sizeof (*c), &(compare_handler)); - /* Arguments. */ - while (*line && ev->argsz < IRC_ARGS_MAX) { - /* Last argument: read until end. */ - if (*line == ':') { - strlcpy(ev->args[ev->argsz++], ++line, IRC_MESSAGE_MAX); - break; - } - - if (!scan(ev, &line)) - return; - } - - convert(s, ev); + if (c) + c->handle(s, ev); } static void @@ -489,10 +392,12 @@ { s->state = IRC_SERVER_STATE_CONNECTED; + if (s->password[0]) + irc_server_send(s, "PASS %s", s->password); + irc_server_send(s, "NICK %s", s->nickname); irc_server_send(s, "USER %s %s %s :%s", s->username, s->username, s->username, s->realname); - /* TODO: server password as well. */ } #if defined(IRCCD_WITH_SSL) @@ -756,8 +661,11 @@ static void flush_ready(struct irc_server *s, const struct pollfd *pfd) { - if (pfd->revents & POLLERR || pfd->revents & POLLHUP) + if (pfd->revents & POLLERR || pfd->revents & POLLHUP) { clear(s); + return; + } + if (pfd->revents & POLLIN) input(s); if (pfd->revents & POLLOUT) @@ -843,8 +751,8 @@ memset(ev, 0, sizeof (*ev)); ev->type = IRC_EVENT_UNKNOWN; - if (length > 0) - parse(s, ev, s->in); + if (length > 0 && irc_event_parse(&ev->msg, s->in)) + handle(s, ev); memmove(s->in, pos + 2, sizeof (s->in) - (length + 2));
--- a/lib/irccd/server.h Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/server.h Wed Jan 20 12:32:59 2021 +0100 @@ -47,8 +47,9 @@ enum irc_server_flags { IRC_SERVER_FLAGS_SSL = (1 << 0), IRC_SERVER_FLAGS_AUTO_REJOIN = (1 << 1), - IRC_SERVER_FLAGS_IPV4 = (1 << 2), - IRC_SERVER_FLAGS_IPV6 = (1 << 3) + IRC_SERVER_FLAGS_JOIN_INVITE = (1 << 2), + IRC_SERVER_FLAGS_IPV4 = (1 << 3), + IRC_SERVER_FLAGS_IPV6 = (1 << 4) }; struct irc_server_prefix { @@ -70,6 +71,7 @@ /* Connection settings. */ char name[IRC_NAME_MAX]; char hostname[IRC_HOST_MAX]; + char password[IRC_PASSWORD_MAX]; unsigned short port; enum irc_server_flags flags;
--- a/lib/irccd/util.c Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/util.c Wed Jan 20 12:32:59 2021 +0100 @@ -115,3 +115,27 @@ return ret; } + +size_t +irc_util_split(char *line, const char **args, size_t max) +{ + size_t idx; + + if (!*line) + return 0; + + for (idx = 0; idx < max; ++idx) { + char *sp = strchr(line, ' '); + + if (!sp || idx + 1 >= max) { + args[idx++] = line; + break; + } + + *sp = '\0'; + args[idx] = line; + line = sp + 1; + } + + return idx; +}
--- a/lib/irccd/util.h Sun Jan 17 19:05:39 2021 +0100 +++ b/lib/irccd/util.h Wed Jan 20 12:32:59 2021 +0100 @@ -47,4 +47,7 @@ char * irc_util_dirname(const char *); +size_t +irc_util_split(char *, const char **, size_t); + #endif /* !IRCCD_UTIL_H */
--- a/tests/CMakeLists.txt Sun Jan 17 19:05:39 2021 +0100 +++ b/tests/CMakeLists.txt Wed Jan 20 12:32:59 2021 +0100 @@ -22,6 +22,7 @@ TESTS test-bot test-channel + test-event #test-dl-plugin test-log test-rule
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-event.c Wed Jan 20 12:32:59 2021 +0100 @@ -0,0 +1,75 @@ +/* + * test-event.c -- test event.h functions + * + * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/event.h> +#include <irccd/server.h> + +GREATEST_TEST +basics_parse_simple(void) +{ + /* This is a TOPIC message. */ + char str[] = ":malikania.fr 332 boris #test :Welcome to #test :: a testing channel"; + struct irc_event_msg msg; + + irc_event_parse(&msg, str); + + GREATEST_ASSERT_STR_EQ("malikania.fr", msg.prefix); + GREATEST_ASSERT_STR_EQ("332", msg.cmd); + GREATEST_ASSERT_STR_EQ("boris", msg.args[0]); + GREATEST_ASSERT_STR_EQ("#test", msg.args[1]); + GREATEST_ASSERT_STR_EQ("Welcome to #test :: a testing channel", msg.args[2]); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_parse_noprefix(void) +{ + /* Ping messages usually don't have a prefix. */ + char str[] = "PING :malikania.fr"; + struct irc_event_msg msg; + + irc_event_parse(&msg, str); + + GREATEST_ASSERT(!msg.prefix); + GREATEST_ASSERT_STR_EQ("PING", msg.cmd); + GREATEST_ASSERT_STR_EQ("malikania.fr", msg.args[0]); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_RUN_TEST(basics_parse_simple); + GREATEST_RUN_TEST(basics_parse_noprefix); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +}