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);