Mercurial > irccd
changeset 977:2da5064d0cff
irccd: add hooks
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 09 Feb 2021 20:10:00 +0100 |
parents | f5a07c0768c8 |
children | 74af4d672842 |
files | irccd/conf.y irccd/lex.l irccd/peer.c lib/CMakeLists.txt lib/irccd/hook.c lib/irccd/hook.h lib/irccd/irccd.c lib/irccd/irccd.h |
diffstat | 8 files changed, 400 insertions(+), 17 deletions(-) [+] |
line wrap: on
line diff
--- a/irccd/conf.y Tue Feb 09 20:08:00 2021 +0100 +++ b/irccd/conf.y Tue Feb 09 20:10:00 2021 +0100 @@ -137,6 +137,7 @@ %token T_COMMA %token T_CONFIG %token T_EVENTS +%token T_HOOK %token T_HOSTNAME %token T_IDENT %token T_LOCATION @@ -181,6 +182,7 @@ | transport | rule | plugin + | hook ; log_verbosity @@ -547,6 +549,16 @@ } ; +hook + : T_HOOK T_STRING T_TO T_STRING + { + irc_bot_hook_add(irc_hook_new($2, $4)); + + free($2); + free($4); + } + ; + %% void
--- a/irccd/lex.l Tue Feb 09 20:08:00 2021 +0100 +++ b/irccd/lex.l Tue Feb 09 20:10:00 2021 +0100 @@ -32,6 +32,7 @@ channels channels config config events events +hook hook hostname hostname ident ident location location @@ -75,6 +76,7 @@ {comma} return T_COMMA; {config} return T_CONFIG; {events} return T_EVENTS; +{hook} return T_HOOK; {hostname} return T_HOSTNAME; {ident} return T_IDENT; {location} return T_LOCATION;
--- a/irccd/peer.c Tue Feb 09 20:08:00 2021 +0100 +++ b/irccd/peer.c Tue Feb 09 20:10:00 2021 +0100 @@ -181,6 +181,72 @@ } /* + * HOOK-ADD name path fprintf(fp, "OK "); + + LIST_FOREACH(s, &irc.servers, link) { + fprintf(fp, "%s", s->name); + + if (LIST_NEXT(s, link)) + fputc(' ', fp); + } + */ +static int +cmd_hook_add(struct peer *p, char *line) +{ + const char *args[2] = {0}; + + if (parse(line, args, 2) != 2) + return EINVAL; + if (irc_bot_hook_get(args[0])) + return EEXIST; + + irc_bot_hook_add(irc_hook_new(args[0], args[1])); + + return ok(p); +} + +/* + * HOOK-LIST + */ +static int +cmd_hook_list(struct peer *p, char *line) +{ + (void)line; + + struct irc_hook *h; + char out[IRC_BUF_LEN]; + FILE *fp; + + if (!(fp = fmemopen(out, sizeof (out) - 1, "w"))) + return errno; + + fprintf(fp, "OK "); + + LIST_FOREACH(h, &irc.hooks, link) { + fprintf(fp, "%s", h->name); + + if (LIST_NEXT(h, link)) + fputc(' ', fp); + } + + fclose(fp); + peer_send(p, out); + + return 0; +} + +/* + * HOOK-REMOVE name + */ +static int +cmd_hook_remove(struct peer *p, char *line) +{ + irc_bot_hook_remove(line); + + return ok(p); +} + +/* * PLUGIN-CONFIG plugin [var [value]] */ static int @@ -834,6 +900,9 @@ const char *name; int (*call)(struct peer *, char *); } cmds[] = { + { "HOOK-ADD", cmd_hook_add }, + { "HOOK-LIST", cmd_hook_list }, + { "HOOK-REMOVE", cmd_hook_remove }, { "PLUGIN-CONFIG", cmd_plugin_config }, { "PLUGIN-INFO", cmd_plugin_info }, { "PLUGIN-LIST", cmd_plugin_list },
--- a/lib/CMakeLists.txt Tue Feb 09 20:08:00 2021 +0100 +++ b/lib/CMakeLists.txt Tue Feb 09 20:10:00 2021 +0100 @@ -28,6 +28,8 @@ irccd/conn.h irccd/event.c irccd/event.h + irccd/hook.c + irccd/hook.h irccd/irccd.c irccd/irccd.h irccd/limits.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/hook.c Tue Feb 09 20:10:00 2021 +0100 @@ -0,0 +1,158 @@ +/* + * hook.c -- irccd hooks + * + * 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 <compat.h> + +#include <sys/wait.h> +#include <assert.h> +#include <errno.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "event.h" +#include "hook.h" +#include "log.h" +#include "server.h" +#include "util.h" + +static char ** +alloc(const struct irc_hook *h, size_t n, ...) +{ + char **ret; + va_list ap; + + ret = irc_util_calloc(n + 2, sizeof (*ret)); + ret[0] = (char *)h->path; + + va_start(ap, n); + + for (size_t i = 0; i < n; ++i) + ret[i + 1] = va_arg(ap, char *); + + va_end(ap); + + return ret; +} + +static char ** +make_args(const struct irc_hook *h, const struct irc_event *ev) +{ + char **ret; + + switch (ev->type) { + case IRC_EVENT_CONNECT: + ret = alloc(h, 2, "onConnect", ev->server->name); + break; + case IRC_EVENT_DISCONNECT: + ret = alloc(h, 2, "onDisconnect", ev->server->name); + break; + case IRC_EVENT_INVITE: + ret = alloc(h, 3, "onInvite", ev->server->name, ev->invite.origin, + ev->invite.channel); + break; + case IRC_EVENT_JOIN: + ret = alloc(h, 3, "onJoin", ev->server->name, ev->join.origin, + ev->join.channel); + break; + case IRC_EVENT_KICK: + ret = alloc(h, 5, "onKick", ev->server->name, ev->kick.origin, + ev->kick.channel, ev->kick.target, ev->kick.reason); + break; + case IRC_EVENT_ME: + ret = alloc(h, 4, "onMe", ev->server->name, ev->message.origin, + ev->message.channel, ev->message.message); + break; + case IRC_EVENT_MESSAGE: + ret = alloc(h, 4, "onMessage", ev->server->name, ev->message.origin, + ev->message.channel, ev->message.message); + break; + case IRC_EVENT_MODE: + ret = alloc(h, 7, "onMode", ev->server->name, ev->mode.origin, + ev->mode.channel, ev->mode.mode, ev->mode.limit, + ev->mode.user, ev->mode.mask); + break; + case IRC_EVENT_NICK: + ret = alloc(h, 3, "onNick", ev->server->name, ev->nick.origin, + ev->nick.nickname); + break; + case IRC_EVENT_NOTICE: + ret = alloc(h, 4, "onNotice", ev->server->name, ev->notice.origin, + ev->notice.channel, ev->notice.notice); + break; + case IRC_EVENT_PART: + ret = alloc(h, 4, "onPart", ev->server->name, ev->part.origin, + ev->part.channel, ev->part.reason); + break; + case IRC_EVENT_TOPIC: + ret = alloc(h, 4, "onTopic", ev->server->name, ev->topic.origin, + ev->topic.channel, ev->topic.topic); + break; + default: + return NULL; + } + + return ret; +} + +struct irc_hook * +irc_hook_new(const char *name, const char *path) +{ + assert(name); + assert(path); + + struct irc_hook *h; + + h = irc_util_malloc(sizeof (*h)); + strlcpy(h->name, name, sizeof (h->name)); + strlcpy(h->path, path, sizeof (h->path)); + + return h; +} + +void +irc_hook_invoke(struct irc_hook *h, const struct irc_event *ev) +{ + char **args; + pid_t child; + + if (!(args = make_args(h, ev))) + return; + + switch ((child = fork())) { + case -1: + irc_log_warn("hook %s: %s", h->name, strerror(errno)); + break; + case 0: + execv(h->path, args); + irc_log_warn("hook %s: %s", h->name, strerror(errno)); + break; + default: + /* We wait for signal handler using SIGCHLD. */ + break; + } + + free(args); +} + +void +irc_hook_finish(struct irc_hook *h) +{ + free(h); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/hook.h Tue Feb 09 20:10:00 2021 +0100 @@ -0,0 +1,46 @@ +/* + * hook.h -- irccd hooks + * + * 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. + */ + +#ifndef IRCCD_HOOK_H +#define IRCCD_HOOK_H + +#include <sys/queue.h> +#include <limits.h> + +#include "limits.h" + +struct irc_event; + +struct irc_hook { + char name[IRC_ID_LEN]; + char path[PATH_MAX]; + LIST_ENTRY(irc_hook) link; +}; + +LIST_HEAD(irc_hook_list, irc_hook); + +struct irc_hook * +irc_hook_new(const char *, const char *); + +void +irc_hook_invoke(struct irc_hook *, const struct irc_event *); + +void +irc_hook_finish(struct irc_hook *); + +#endif /* !IRCCD_HOOK_H */
--- a/lib/irccd/irccd.c Tue Feb 09 20:08:00 2021 +0100 +++ b/lib/irccd/irccd.c Tue Feb 09 20:10:00 2021 +0100 @@ -23,6 +23,7 @@ #include <err.h> #include <errno.h> #include <poll.h> +#include <signal.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -47,6 +48,7 @@ }; static int pipes[2]; +static struct sigaction sa; static int is_command(const struct irc_plugin *p, const struct irc_event *ev) @@ -66,23 +68,19 @@ } static struct irc_event * -to_command(const struct irc_plugin *p, struct irc_event *ev) +to_command(const struct irc_plugin *p, const struct irc_event *ev) { - char *action; + static struct irc_event cev; /* Convert "!test foo bar" to "foo bar" */ - action = ev->message.message + strlen(ev->server->commandchar) + strlen(p->name); - - while (*action && isspace(*action)) - ++action; + memcpy(&cev, ev, sizeof (*ev)); + cev.type = IRC_EVENT_COMMAND; + cev.message.message += strlen(cev.server->commandchar) + strlen(p->name); - action = strdup(action); - free(ev->message.message); + while (*cev.message.message && isspace(*cev.message.message)) + ++cev.message.message; - ev->type = IRC_EVENT_COMMAND; - ev->message.message = action; - - return ev; + return &cev; } static int @@ -141,9 +139,13 @@ } static void -invoke(struct irc_event *ev) +invoke(const struct irc_event *ev) { - struct irc_plugin *p, *tmp, *plgcmd = NULL; + struct irc_plugin *p, *ptmp, *plgcmd = NULL; + struct irc_hook *h, *htmp; + + LIST_FOREACH_SAFE(h, &irc.hooks, link, htmp) + irc_hook_invoke(h, ev); /* * Invoke for every plugin the event verbatim. Then, the event may match @@ -159,7 +161,7 @@ * onMessage for hangman and logger but onCommand for ask. As such call * hangman and logger first and modify event before ask. */ - LIST_FOREACH_SAFE(p, &irc.plugins, link, tmp) { + LIST_FOREACH_SAFE(p, &irc.plugins, link, ptmp) { if (is_command(p, ev)) plgcmd = p; else if (invokable(p, ev)) @@ -209,6 +211,29 @@ return irc_plugin_loader_open(ldr, path); } +static void +handle_sigchld(int signum, siginfo_t *sinfo, void *unused) +{ + (void)signum; + (void)unused; + + int status; + + if (sinfo->si_code != CLD_EXITED) + return; + + if (waitpid(sinfo->si_pid, &status, 0) < 0) { + irc_log_warn("irccd: %s", strerror(errno)); + return; + } + + if (WIFEXITED(status)) + irc_log_debug("irccd: hook %d terminated correctly", sinfo->si_pid); + else + irc_log_debug("irccd: hook process %d terminated abnormally: %d", + sinfo->si_pid, WEXITSTATUS(status)); +} + void irc_bot_init(void) { @@ -216,6 +241,14 @@ if (pipe(pipes) < 0) err(1, "pipe"); + + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = handle_sigchld; + + sigemptyset(&sa.sa_mask); + + if (sigaction(SIGCHLD, &sa, NULL) < 0) + err(1, "sigaction"); } void @@ -474,6 +507,50 @@ TAILQ_INIT(&irc.rules); } +void +irc_bot_hook_add(struct irc_hook *h) +{ + assert(h); + assert(!irc_bot_hook_get(h->name)); + + LIST_INSERT_HEAD(&irc.hooks, h, link); +} + +struct irc_hook * +irc_bot_hook_get(const char *name) +{ + struct irc_hook *h; + + LIST_FOREACH(h, &irc.hooks, link) + if (strcmp(h->name, name) == 0) + return h; + + return NULL; +} + +void +irc_bot_hook_remove(const char *name) +{ + assert(name); + + struct irc_hook *h; + + if ((h = irc_bot_hook_get(name))) { + LIST_REMOVE(h, link); + irc_hook_finish(h); + } +} + +void +irc_bot_hook_clear(void) +{ + struct irc_hook *h, *tmp; + + LIST_FOREACH_SAFE(h, &irc.hooks, link, tmp) + irc_hook_finish(h); + LIST_INIT(&irc.hooks); +} + size_t irc_bot_poll_count(void) { @@ -520,9 +597,12 @@ { struct irc_server *s; - LIST_FOREACH(s, &irc.servers, link) - if (irc_server_poll(s, ev)) + LIST_FOREACH(s, &irc.servers, link) { + if (irc_server_poll(s, ev)) { + invoke(ev); return 1; + } + } return 0; }
--- a/lib/irccd/irccd.h Tue Feb 09 20:08:00 2021 +0100 +++ b/lib/irccd/irccd.h Tue Feb 09 20:10:00 2021 +0100 @@ -21,6 +21,7 @@ #include <stddef.h> +#include "hook.h" #include "plugin.h" #include "rule.h" #include "server.h" @@ -30,6 +31,7 @@ struct irc_plugin_list plugins; struct irc_plugin_loader_list plugin_loaders; struct irc_rule_list rules; + struct irc_hook_list hooks; } irc; void @@ -83,6 +85,18 @@ void irc_bot_rule_clear(void); +void +irc_bot_hook_add(struct irc_hook *); + +struct irc_hook * +irc_bot_hook_get(const char *); + +void +irc_bot_hook_remove(const char *); + +void +irc_bot_hook_clear(void); + size_t irc_bot_poll_count(void);