# HG changeset patch # User David Demelier # Date 1610396758 -3600 # Node ID 7b74df7e891318cf4e30302ef06fb65fad6d9220 # Parent ffe9853085677ff32f16f4bf026521cbfa364ae9 irccd: native plugin support diff -r ffe985308567 -r 7b74df7e8913 .hgignore --- a/.hgignore Mon Jan 11 10:28:49 2021 +0100 +++ b/.hgignore Mon Jan 11 21:25:58 2021 +0100 @@ -24,8 +24,11 @@ \.a$ \.o$ \.d$ +\.so$ +\.dylib$ # tests. +^tests/test-dl-plugin$ ^tests/test-log$ ^tests/test-subst$ ^tests/test-util$ diff -r ffe985308567 -r 7b74df7e8913 Makefile --- a/Makefile Mon Jan 11 10:28:49 2021 +0100 +++ b/Makefile Mon Jan 11 21:25:58 2021 +0100 @@ -16,58 +16,107 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -.POSIX: - .SUFFIXES: .SUFFIXES: .o .c include config.mk -IRCCD= irccd/irccd -IRCCD_SRCS= extern/libduktape/duktape.c \ - irccd/log.c \ - irccd/server.c \ - irccd/subst.c \ - irccd/util.c -IRCCD_OBJS= ${IRCCD_SRCS:.c=.o} -IRCCD_DEPS= ${IRCCD_SRCS:.c=.d} +IRCCD= irccd/irccd +IRCCD_SRCS= irccd/main.c +IRCCD_OBJS= ${IRCCD_SRCS:.c=.o} +IRCCD_DEPS= ${IRCCD_SRCS:.c=.d} + +LIBCOMPAT= extern/libcompat/libirccd-compat.a + +ifeq (${WITH_JS},yes) +LIBDUKTAPE= extern/libduktape/libirccd-duktape.a +endif + +LIBIRCCD= lib/libirccd.a +LIBIRCCD_SRCS= lib/irccd/dl-plugin.c +LIBIRCCD_SRCS+= lib/irccd/log.c +LIBIRCCD_SRCS+= lib/irccd/plugin.c +LIBIRCCD_SRCS+= lib/irccd/server.c +LIBIRCCD_SRCS+= lib/irccd/subst.c +LIBIRCCD_SRCS+= lib/irccd/util.c +LIBIRCCD_OBJS= ${LIBIRCCD_SRCS:.c=.o} +LIBIRCCD_DEPS= ${LIBIRCCD_SRCS:.c=.d} -TESTS= tests/test-log.c \ - tests/test-util.c \ - tests/test-subst.c -TESTS_OBJS= ${TESTS:.c=} +TESTS= tests/test-dl-plugin.c +TESTS+= tests/test-log.c +TESTS+= tests/test-util.c +TESTS+= tests/test-subst.c +TESTS_OBJS= ${TESTS:.c=} + +DEFINES= -D_BSD_SOURCE +DEFINES+= -DSOURCEDIR=\"`pwd`\" -FLAGS= -D_BSD_SOURCE \ - -I extern/libduktape \ - -I extern/libgreatest \ - -I extern/libcompat/include \ - -I . +INCS= -I extern/libcompat/include +ifeq (${WITH_JS},yes) +INCS+= -I extern/libduktape +endif +INCS+= -I extern/libgreatest +INCS+= -I lib + +LIBS= -L extern/libcompat +ifeq (${WITH_JS},yes) +LIBS+= -L extern/libduktape +endif +LIBS+= -L lib + +LIBS+= -l irccd-compat +ifeq (${WITH_JS},yes) +LIBS+= -l irccd-duktape +endif +LIBS+= -l irccd all: ${IRCCD} .c.o: - ${CC} -MMD ${FLAGS} ${CFLAGS} -c $< -o $@ + ${CMD.cc} -.c: - ${CC} ${FLAGS} ${CFLAGS} $< -o $@ extern/libcompat/libcompat.a ${IRCCD_OBJS} ${LDFLAGS} - +-include ${LIBIRCCD_DEPS} -include ${IRCCD_DEPS} -extern/libcompat/libcompat.a: - ${MAKE} -C extern/libcompat +${LIBCOMPAT}: + ${MAKE} CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" -C extern/libcompat -${IRCCD_OBJS}: extern/libcompat/libcompat.a +ifeq (${WITH_JS},yes) +${LIBDUKTAPE}: + ${MAKE} CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" -C extern/libduktape +endif + +${LIBIRCCD_OBJS}: ${LIBCOMPAT} + +${LIBIRCCD}: ${LIBIRCCD_OBJS} + ${CMD.ar} -${IRCCD}: irccd/main.o ${IRCCD_OBJS} - ${CC} -o $@ extern/libcompat/libcompat.a irccd/main.o ${IRCCD_OBJS} ${LDFLAGS} +${IRCCD}: ${IRCCD_OBJS} ${LIBCOMPAT} ${LIBDUKTAPE} ${LIBIRCCD} + ${CMD.ccld} -clean: - ${MAKE} -C extern/libcompat clean - rm -f irccd/main.o irccd/main.d ${IRCCD} ${IRCCD_OBJS} ${IRCCD_DEPS} +# Unit tests. +tests/test-%.o: tests/test-%.c + ${CMD.cc} +tests/test-%: tests/test-%.o ${LIBCOMPAT} ${IRCCD_OBJS} + ${CMD.ccld} -${TESTS_OBJS}: ${IRCCD_OBJS} +${TESTS_OBJS}: ${LIBIRCCD} + +# Sample plugin for test-dl-plugin. +tests/example-dl-plugin${EXT.shared}: tests/example-dl-plugin.o + ${CMD.ld-shared} + +tests/test-dl-plugin: tests/example-dl-plugin${EXT.shared} tests: ${TESTS_OBJS} for t in ${TESTS_OBJS}; do ./$$t; done +clean: + ${MAKE} -C extern/libcompat clean + ${MAKE} -C extern/libduktape clean + rm -f ${LIBIRCCD} ${LIBIRCCD_OBJS} ${LIBIRCCD_DEPS} + rm -f ${IRCCD} ${IRCCD_OBJS} ${IRCCD_DEPS} + rm -f tests/example-dl-plugin${EXT.shared} tests/example-dl-plugin.o tests/example-dl-plugin.d + rm -f ${TESTS_OBJS} + .PHONY: all clean tests diff -r ffe985308567 -r 7b74df7e8913 config.mk --- a/config.mk Mon Jan 11 10:28:49 2021 +0100 +++ b/config.mk Mon Jan 11 21:25:58 2021 +0100 @@ -16,8 +16,30 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -CC= cc +# Build tools. +CC= cc +AR= ar + +# Installation paths. +PREFIX= /usr/local +BINDIR= bin + +# User options. +WITH_JS= yes + +# System dependant macros. +OS:= $(shell uname -s) -# Disable in release. -CFLAGS= -Wall -Wextra -g -O0 -fsanitize=address,undefined -LDFLAGS= -fsanitize=address,undefined +# Standard commands. +CMD.cc= ${CC} ${DEFINES} ${INCS} ${CFLAGS} -MMD -c $< -o $@ +CMD.ccld= ${CC} ${DEFINES} ${INCS} ${CFLAGS} -o $@ $< ${LIBS} ${LDFLAGS} +CMD.ar= ${AR} -rc $@ $^ + +# Determine shared library extension and command to generate. +ifeq (Darwin,${OS}) +EXT.shared= .dylib +CMD.ld-shared= ${CC} -dynamiclib -o $@ $^ ${LDFLAGS} +else +EXT.shared= .so +CMD.ld-shared= ${CC} -shared -o $@ $^ ${LDFLAGS} +endif diff -r ffe985308567 -r 7b74df7e8913 extern/libcompat/Makefile --- a/extern/libcompat/Makefile Mon Jan 11 10:28:49 2021 +0100 +++ b/extern/libcompat/Makefile Mon Jan 11 21:25:58 2021 +0100 @@ -57,7 +57,7 @@ FUNCTIONS_OBJS= ${FUNCTIONS_SRCS:.c=.o} FUNCTIONS_HDRS= ${FUNCTIONS_SRCS:.c=.h} -LIB= libcompat.a +LIB= libirccd-compat.a all: ${LIB} @@ -69,7 +69,7 @@ @CC="${CC}" CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" ./trycompile -c ${*F} .c.h: - @CC="${CC}" CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" ./trycompile ${*F} + CC="${CC}" CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" ./trycompile ${*F} ${FUNCTIONS_HDRS} ${FUNCTIONS_OBJS}: trycompile diff -r ffe985308567 -r 7b74df7e8913 extern/libduktape/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extern/libduktape/Makefile Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,32 @@ +# +# Makefile -- POSIX makefile for irccd +# +# Copyright (c) 2013-2021 David Demelier +# +# 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 ../../config.mk + +all: libirccd-duktape.a + +duktape.o: duktape.c duktape.h duk_config.h + ${CC} ${CFLAGS} -c $< -o $@ + +libirccd-duktape.a: duktape.o + ${CMD.ar} + +clean: + rm -f libirccd-duktape.a duktape.o + +.PHONY: clean diff -r ffe985308567 -r 7b74df7e8913 irccd/event.h --- a/irccd/event.h Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -/* - * event.h -- IRC event - * - * Copyright (c) 2013-2021 David Demelier - * - * 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_EVENT_H -#define IRCCD_EVENT_H - -#include - -#include "limits.h" - -struct irc_server; - -enum irc_event_type { - IRC_EVENT_UNKNOWN, - IRC_EVENT_CONNECT, - IRC_EVENT_DISCONNECT, - IRC_EVENT_INVITE, - IRC_EVENT_JOIN, - IRC_EVENT_KICK, - IRC_EVENT_ME, - IRC_EVENT_MESSAGE, - IRC_EVENT_MODE, - IRC_EVENT_NAMES, - IRC_EVENT_NICK, - IRC_EVENT_NOTICE, - IRC_EVENT_PART, - IRC_EVENT_TOPIC, - IRC_EVENT_WHOIS -}; - -struct irc_event { - enum irc_event_type type; - struct irc_server *server; - - /* - * Raw arguments. - * [0]: prefix - */ - 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 { - 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; - }; -}; - -#endif /* !IRCCD_EVENT_H */ diff -r ffe985308567 -r 7b74df7e8913 irccd/limits.h --- a/irccd/limits.h Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -/* - * limits.h -- irccd limits - * - * Copyright (c) 2013-2021 David Demelier - * - * 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_LIMITS_H -#define IRCCD_LIMITS_H - -/* IRC limits. */ -#define IRC_NICKNAME_MAX 32 -#define IRC_USERNAME_MAX 32 -#define IRC_REALNAME_MAX 64 -#define IRC_CHANNEL_MAX 64 -#define IRC_PASSWORD_MAX 64 -#define IRC_CTCPVERSION_MAX 128 -#define IRC_USERMODES_MAX 16 - -#define IRC_MESSAGE_MAX 512 -#define IRC_ARGS_MAX 16 - -/* Network limits. */ -#define IRC_HOST_MAX 32 -#define IRC_BUF_MAX 8192 - -/* Types limits. */ -#define IRC_NAME_MAX 16 - -#endif /* !IRCCD_LIMITS_H */ diff -r ffe985308567 -r 7b74df7e8913 irccd/log.c --- a/irccd/log.c Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* - * log.c -- loggers - * - * Copyright (c) 2013-2021 David Demelier - * - * 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 -#include -#include -#include -#include -#include - -#include "log.h" - -enum level { - LEVEL_INFO, - LEVEL_WARN, - LEVEL_DEBUG -}; - -static FILE *out, *err; -static bool verbosity; - -static void -handler_files(enum level level, const char *fmt, va_list ap) -{ - switch (level) { - case LEVEL_WARN: - vfprintf(err, fmt, ap); - putc('\n', err); - fflush(err); - break; - default: - vfprintf(out, fmt, ap); - putc('\n', out); - fflush(out); - break; - } -} - -static void -finalizer_files(void) -{ - if (out) - fclose(out); - if (err) - fclose(err); -} - -static void -handler_syslog(enum level level, const char *fmt, va_list ap) -{ - static const int table[] = { - [LEVEL_INFO] = LOG_INFO, - [LEVEL_WARN] = LOG_WARNING, - [LEVEL_DEBUG] = LOG_DEBUG - }; - - /* TODO: add compatibility shim for vsyslog. */ - vsyslog(table[level], fmt, ap); -} - -static void -finalizer_syslog(void) -{ - closelog(); -} - -static void (*handler)(enum level, const char *, va_list); -static void (*finalizer)(void); - -void -irc_log_to_syslog(void) -{ - irc_log_finish(); - - openlog("irccd", 0, LOG_DAEMON); - - handler = handler_syslog; - finalizer = finalizer_syslog; -} - -void -irc_log_to_console(void) -{ - irc_log_finish(); - - out = stdout; - err = stderr; - - handler = handler_files; - finalizer = NULL; -} - -void -irc_log_to_files(const char *stdout, const char *stderr) -{ - irc_log_finish(); - - if (!(out = fopen(stdout, "a"))) - irc_log_warn("%s: %s", stdout, strerror(errno)); - if (!(err = fopen(stderr, "a"))) - irc_log_warn("%s: %s", stdout, strerror(errno)); - - handler = handler_files; - finalizer = finalizer_files; -} - -void -irc_log_to_null(void) -{ - irc_log_finish(); - - handler = NULL; - finalizer = NULL; -} - -void -irc_log_set_verbose(bool mode) -{ - verbosity = mode; -} - -void -irc_log_info(const char *fmt, ...) -{ - assert(fmt); - - va_list ap; - - va_start(ap, fmt); - - if (verbosity && handler) - handler(LEVEL_INFO, fmt, ap); - - va_end(ap); -} - -void -irc_log_warn(const char *fmt, ...) -{ - assert(fmt); - - va_list ap; - - va_start(ap, fmt); - - if (handler) - handler(LEVEL_WARN, fmt, ap); - - va_end(ap); -} - -void -irc_log_debug(const char *fmt, ...) -{ -#if !defined(NDBEUG) - assert(fmt); - - va_list ap; - - va_start(ap, fmt); - - if (handler) - handler(LEVEL_DEBUG, fmt, ap); - - va_end(ap); -#else - (void)fmt; -#endif -} - -void -irc_log_finish(void) -{ - if (finalizer) - finalizer(); - - handler = NULL; - finalizer = NULL; -} diff -r ffe985308567 -r 7b74df7e8913 irccd/log.h --- a/irccd/log.h Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -/* - * log.h -- loggers - * - * Copyright (c) 2013-2021 David Demelier - * - * 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_LOG_H -#define IRCCD_LOG_H - -#include - -void -irc_log_to_syslog(void); - -void -irc_log_to_console(void); - -void -irc_log_to_files(const char *, const char *); - -void -irc_log_to_null(void); - -void -irc_log_set_verbose(bool); - -void -irc_log_info(const char *, ...); - -void -irc_log_warn(const char *, ...); - -void -irc_log_debug(const char *, ...); - -void -irc_log_finish(void); - -#endif /* !IRCCD_LOG_H */ diff -r ffe985308567 -r 7b74df7e8913 irccd/main.c --- a/irccd/main.c Mon Jan 11 10:28:49 2021 +0100 +++ b/irccd/main.c Mon Jan 11 21:25:58 2021 +0100 @@ -20,8 +20,8 @@ #include #include -#include "event.h" -#include "server.h" +#include +#include int main(int argc, char **argv) @@ -59,6 +59,8 @@ printf("me, origin=%s,channel=%s,message=%s\n", ev.me.origin,ev.me.channel, ev.me.message); break; + default: + break; } } } diff -r ffe985308567 -r 7b74df7e8913 irccd/server.c --- a/irccd/server.c Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,955 +0,0 @@ -/* - * server.c -- an IRC server - * - * Copyright (c) 2013-2021 David Demelier - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(IRCCD_HAVE_SSL) -# include -#endif - -#include "event.h" -#include "log.h" -#include "server.h" -#include "util.h" - -struct origin { - char nickname[IRC_NICKNAME_MAX]; - char username[IRC_USERNAME_MAX]; - 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 -compare_chan(const void *d1, const void *d2) -{ - return strcmp( - ((const struct irc_server_channel *)d1)->name, - ((const struct irc_server_channel *)d2)->name - ); -} - -static const struct origin * -parse_origin(const char *prefix) -{ - static struct origin origin; - char fmt[128]; - - memset(&origin, 0, sizeof (origin)); - snprintf(fmt, sizeof (fmt), "%%%zu[^!]!%%%zu[^@]@%%%zus", - sizeof (origin.nickname) - 1, - sizeof (origin.username) - 1, - sizeof (origin.host) - 1); - sscanf(prefix, fmt, origin.nickname, origin.username, origin.host); - - return &origin; -} - -static inline void -sort(struct irc_server *s) -{ - qsort(s->channels, s->channelsz, sizeof (*s->channels), compare_chan); -} - -static struct irc_server_channel * -add_channel(struct irc_server *s, const char *name, const char *password, bool joined) -{ - struct irc_server_channel ch = { - .joined = joined - }; - - strlcpy(ch.name, name, sizeof (ch.name)); - - if (password) - strlcpy(ch.password, password, sizeof (ch.password)); - - s->channels = irc_util_reallocarray(s->channels, ++s->channelsz, sizeof (ch)); - - memcpy(&s->channels[s->channelsz - 1], &ch, sizeof (ch)); - sort(s); - - return irc_server_find(s, name); -} - -static void -remove_channel(struct irc_server *s, struct irc_server_channel *ch) -{ - /* Null channel name will be moved at the end. */ - memset(ch, 0, sizeof (*ch)); - - s->channels = irc_util_reallocarray(s->channels, --s->channelsz, sizeof (*ch)); - sort(s); -} - -static void -read_support_prefix(struct irc_server *s, const char *value) -{ - char modes[16 + 1] = { 0 }; - char tokens[16 + 1] = { 0 }; - - if (sscanf(value, "(%16[^)])%16s", modes, tokens) == 2) { - char *pm = modes; - char *tk = tokens; - - for (size_t i = 0; i < 16 && *pm && *tk; ++i) { - s->prefixes[i].mode = *pm++; - s->prefixes[i].token = *tk++; - } - } -} - -static void -read_support_chantypes(struct irc_server *s, const char *value) -{ - strlcpy(s->chantypes, value, sizeof (s->chantypes)); -} - -static void -convert_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); -} - -static void -convert_support(struct irc_server *s, struct irc_event *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) - continue; - - if (strcmp(key, "PREFIX") == 0) - read_support_prefix(s, value); - if (strcmp(key, "CHANTYPES") == 0) - read_support_chantypes(s, value); - } -} - -static void -convert_join(struct irc_server *s, struct irc_event *ev) -{ - const struct origin *origin = parse_origin(ev->args[0]); - struct irc_server_channel *ch; - - ev->type = IRC_EVENT_JOIN; - ev->server = s; - ev->join.origin = ev->args[0]; - ev->join.channel = ev->args[2]; - - /* Also add a channel if the bot joined. */ - if (strcmp(s->nickname, origin->nickname)) { - if ((ch = irc_server_find(s, ev->args[2]))) - ch->joined = true; - else - add_channel(s, ev->args[2], NULL, true); - } -} - -static void -convert_kick(struct irc_server *s, struct irc_event *ev) -{ - 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]; - - /* - * 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) { - struct irc_server_channel *ch = irc_server_find(s, ev->args[2]); - - if (ch) { - ch->joined = false; - - if (s->flags & IRC_SERVER_FLAGS_AUTO_REJOIN) - irc_server_join(s, ch->name, ch->password); - } - } -} - -static void -convert_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]); - } - -#if 0 - if (strcmp(m->args[0], s->nickname) == 0) { - /* Own user modes. */ - strlcpy(s->usermodes, m->args[1], sizeof (s->usermodes); - } else { - /* TODO: channel modes. */ - } -#endif -} - -static void -convert_part(struct irc_server *s, struct irc_event *ev) -{ - const struct origin *origin = parse_origin(ev->args[0]); - struct irc_server_channel *ch = irc_server_find(s, ev->args[2]); - - 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) -{ - 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]; - - /* - * Detect CTCP commands which are PRIVMSG with a special boundaries. - * - * Example: - * PRIVMSG jean :\001ACTION I'm eating\001. - */ - if (is_ctcp(ev->args[3])) { - ev->type = IRC_EVENT_ME; - ev->message.message = ctcp(ev->args[3] + 1); - } -} - -static void -convert_nick(struct irc_server *s, struct irc_event *ev) -{ - const struct origin *origin = parse_origin(ev->args[0]); - - /* Update nickname if it is myself. */ - if (strcmp(origin->nickname, s->nickname) == 0) - strlcpy(s->nickname, ev->args[2], 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]; -} - -static void -convert_topic(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]; -} - -static void -convert_ping(struct irc_server *s, struct irc_event *ev) -{ - irc_server_send(s, "PONG %s", ev->args[0]); -} - -static void -convert_names(struct irc_server *s, struct irc_event *ev) -{ - (void)s; - (void)ev; -#if 0 - struct irc_server_channel *chan; - char *p, *n; - - if (m->argsz < 3 || !(chan = irc_server_find(s, m->args[2]))) - return; - - /* - * Message arguments are as following: - * 0------- 1 2------- 3-------------------- - * yourself = #channel nick1 nick2 nick3 ... - */ - for (p = m->args[3]; p; p = n ? n + 1 : NULL) { - if ((n = strpbrk(p, " "))) - *n = 0; - - channel_add(chan, s, p); - } -#endif -} - -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 }, - { "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 } -}; - -static int -compare_converter(const void *d1, const void *d2) -{ - return strcmp(d1, ((const struct convert *)d2)->command); -} - -static void -convert(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)); - - if (c) - c->convert(s, ev); -} - -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++; - - /* Skip optional spaces. */ - while (*p && isspace(*p)) - ++p; - - if (i >= IRC_MESSAGE_MAX) - return false; - - *line = p; - ev->argsz++; - - return true; -} - -static void -parse(struct irc_server *s, struct irc_event *ev, const char *line) -{ - if (!*line || *line++ != ':') - return; - if (!scan(ev, &line)) /* Prefix */ - return; - if (!scan(ev, &line)) /* Command */ - return; - - /* 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); -} - -static void -clear(struct irc_server *s) -{ - s->state = IRC_SERVER_STATE_DISCONNECTED; - - if (s->fd != 0) { - close(s->fd); - s->fd = 0; - } - - if (s->ai) { - freeaddrinfo(s->ai); - s->ai = NULL; - s->aip = NULL; - } - -#if defined(IRCCD_HAVE_SSL) - if (s->ssl) { - SSL_free(s->ssl); - s->ssl = NULL; - } - if (s->ctx) { - SSL_CTX_free(s->ctx); - s->ctx = NULL; - } -#endif -} - -static bool -lookup(struct irc_server *s) -{ - struct addrinfo hints = { - .ai_socktype = SOCK_STREAM, - }; - char service[16]; - int ret; - - snprintf(service, sizeof (service), "%hu", s->port); - - if ((ret = getaddrinfo(s->host, service, &hints, &s->ai)) != 0) - irc_log_warn("server %s: %s", s->name, gai_strerror(ret)); - - s->aip = s->ai; - - return true; -} - -static void -auth(struct irc_server *s) -{ - s->state = IRC_SERVER_STATE_CONNECTED; - - 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_HAVE_SSL) - -static void -secure_update(struct irc_server *s, int ret) -{ - (void)s; - (void)ret; - - assert(s); - - int r; - - if (!(s->flags & SERVER_FL_SSL)) - return; - - switch ((r = SSL_get_error(s->ssl, ret))) { - case SSL_ERROR_WANT_READ: - s->ssl_state = SERVER_SSL_NEED_READ; - break; - case SSL_ERROR_WANT_WRITE: - s->ssl_state = SERVER_SSL_NEED_WRITE; - break; - case SSL_ERROR_SSL: - clear(s); - break; - default: - s->ssl_state = SERVER_SSL_NONE; - break; - } -} - -#endif - -static void -handshake(struct irc_server *s) -{ - assert(s); - - if (!(s->flags & IRC_SERVER_FLAGS_SSL)) - auth(s); - else { -#if defined(IRCCD_HAVE_SSL) - int r; - - s->state = SERVER_ST_HANDSHAKING; - - if ((r = SSL_do_handshake(s->ssl)) > 0) - auth(s); - - secure_update(s, r); -#endif - } -} - -static void -secure_connect(struct irc_server *s) -{ - assert(s); - - if (!(s->flags & IRC_SERVER_FLAGS_SSL)) - handshake(s); - else { -#if defined(IRCCD_HAVE_SSL) - int r; - - if (!s->ctx) - s->ctx = SSL_CTX_new(TLS_method()); - if (!s->ssl) { - s->ssl = SSL_new(s->ctx); - SSL_set_fd(s->ssl, s->fd); - } - - if ((r = SSL_connect(s->ssl)) > 0) - ssl_handshake(s); - - secure_update(s, r); -#endif - } -} - -static void -dial(struct irc_server *s) -{ - /* No more address available. */ - if (s->aip == NULL) { - clear(s); - return; - } - - for (; s->aip; s->aip = s->aip->ai_next) { - /* We may need to close a socket that was open earlier. */ - if (s->fd != 0) - close(s->fd); - - s->fd = socket(s->aip->ai_family, s->aip->ai_socktype, - s->aip->ai_protocol); - - if (s->fd < 0) { - irc_log_warn("server %s: %s", s->name, strerror(errno)); - continue; - } - - /* TODO: is F_GETFL required before? */ - fcntl(s->fd, F_SETFL, O_NONBLOCK); - - /* - * With some luck, the connection completes immediately, - * otherwise we will need to wait until the socket is writable. - */ - if (connect(s->fd, s->aip->ai_addr, s->aip->ai_addrlen) == 0) { - secure_connect(s); - break; - } - - /* Connect failed, check why. */ - switch (errno) { - case EINPROGRESS: - case EAGAIN: - /* Let the writable state to determine. */ - return; - default: - irc_log_warn("server %s: %s", s->name, strerror(errno)); - break; - } - } -} - -static void -input(struct irc_server *s) -{ - char buf[IRC_MESSAGE_MAX] = {0}; - ssize_t nr = 0; - - if (s->flags & IRC_SERVER_FLAGS_SSL) { -#if defined(IRCCD_HAVE_SSL) - nr = SSL_read(s->ssl, buf, sizeof (buf) - 1); - secure_update(s, nr); -#endif - } else { - if ((nr = recv(s->fd, buf, sizeof (buf) - 1, 0)) < 0) - clear(s); - } - - if (nr > 0) { - if (strlcat(s->in, buf, sizeof (s->in)) >= sizeof (s->in)) { - irc_log_warn("server %s: input buffer too long", s->name); - clear(s); - } - } -} - -static void -output(struct irc_server *s) -{ - ssize_t ns = 0; - - if (s->flags & IRC_SERVER_FLAGS_SSL) { -#if defined(IRCCD_HAVE_SSL) - ns = SSL_write(s->ssl, s->out.data, s->out.size); - secure_update(s, ns); -#endif - } else if ((ns = send(s->fd, s->out, strlen(s->out), 0)) <= 0) - clear(s); - - if (ns > 0) { - /* Optimize if everything was sent. */ - if ((size_t)ns >= sizeof (s->out)) - s->out[0] = '\0'; - else - memmove(s->out, s->out + ns, sizeof (s->out) - ns); - } -} - -static void -prepare_connecting(const struct irc_server *s, struct pollfd *pfd) -{ - (void)s; - -#if defined(IRCCD_HAVE_SSL) - if (s->flags & IRC_SERVER_FLAGS_SSL && s->ssl && s->ctx) { - switch (s->ssl_state) { - case IRC_SERVER_SSL_NEED_READ: - pfd->events |= POLLIN; - break; - case IRC_SERVER_SSL_NEED_WRITE: - pfd->events |= POLLOUT; - break; - default: - break; - } - } else -#endif - pfd->events |= POLLOUT; -} - -static void -prepare_ready(const struct irc_server *s, struct pollfd *pfd) -{ -#if defined(IRCCD_HAVE_SSL) - if (s->flags & IRC_SERVER_FLAGS_SSL && s->ssl_state) { - switch (s->ssl_state) { - case SERVER_SSL_NEED_READ: - pfd->events |= POLLIN; - break; - case SERVER_SSL_NEED_WRITE: - pfd->events |= POLLOUT; - break; - default: - break; - } - } else { -#endif - pfd->events |= POLLIN; - - if (s->out[0]) - pfd->events |= POLLOUT; -#if defined(IRCCD_HAVE_SSL) - } -#endif -} - -static void -flush_connecting(struct irc_server *s, const struct pollfd *pfd) -{ - (void)pfd; - - int res, err = -1; - socklen_t len = sizeof (int); - - if ((res = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &err, &len)) < 0 || err) { - irc_log_warn("server %s: %s", s->name, strerror(res ? err : errno)); - dial(s); - } else - secure_connect(s); -} - -static void -flush_handshaking(struct irc_server *s, const struct pollfd *pfd) -{ - (void)pfd; - - handshake(s); -} - -static void -flush_ready(struct irc_server *s, const struct pollfd *pfd) -{ - if (pfd->revents & POLLERR || pfd->revents & POLLHUP) - clear(s); - if (pfd->revents & POLLIN) - input(s); - if (pfd->revents & POLLOUT) - output(s); -} - -static const struct { - void (*prepare)(const struct irc_server *, struct pollfd *); - void (*flush)(struct irc_server *, const struct pollfd *); -} io_table[] = { - [IRC_SERVER_STATE_CONNECTING] = { - prepare_connecting, - flush_connecting - }, - [IRC_SERVER_STATE_HANDSHAKING] = { - prepare_ready, - flush_handshaking - }, - [IRC_SERVER_STATE_CONNECTED] = { - prepare_ready, - flush_ready - }, -}; - -void -irc_server_connect(struct irc_server *s) -{ - assert(s); - - s->state = IRC_SERVER_STATE_CONNECTING; - - if (!lookup(s)) - clear(s); - else - dial(s); -} - -void -irc_server_disconnect(struct irc_server *s) -{ - assert(s); - - clear(s); -} - -void -irc_server_prepare(const struct irc_server *s, struct pollfd *pfd) -{ - pfd->fd = s->fd; - pfd->events = 0; - - if (io_table[s->state].prepare) - io_table[s->state].prepare(s, pfd); -} - -void -irc_server_flush(struct irc_server *s, const struct pollfd *pfd) -{ - if (io_table[s->state].flush) - io_table[s->state].flush(s, pfd); -} - -bool -irc_server_poll(struct irc_server *s, struct irc_event *ev) -{ - assert(s); - assert(ev); - - char *pos; - size_t length; - - if (!(pos = strstr(s->in, "\r\n"))) - return false; - - /* Turn end of the string at delimiter. */ - *pos = 0; - length = pos - s->in; - - /* Clear event in case we don't understand this message. */ - memset(ev, 0, sizeof (*ev)); - ev->type = IRC_EVENT_UNKNOWN; - - if (length > 0) - parse(s, ev, s->in); - - memmove(s->in, pos + 2, sizeof (s->in) - (length + 2)); - - return true; -} - -struct irc_server_channel * -irc_server_find(struct irc_server *s, const char *name) -{ - assert(s); - assert(name); - - struct irc_server_channel key = {0}; - - strlcpy(key.name, name, sizeof (key.name)); - - return bsearch(&key, s->channels, s->channelsz, sizeof (key), compare_chan); -} - -bool -irc_server_send(struct irc_server *s, const char *fmt, ...) -{ - assert(s); - assert(fmt); - - char buf[sizeof (s->out)]; - va_list ap; - size_t len, avail, required; - - va_start(ap, fmt); - required = vsnprintf(buf, sizeof (buf), fmt, ap); - va_end(ap); - - len = strlen(s->out); - avail = sizeof (s->out) - len; - - /* Don't forget \r\n. */ - if (required + 2 >= avail) - return false; - - strlcat(s->out, buf, sizeof (s->out)); - strlcat(s->out, "\r\n", sizeof (s->out)); - - return true; -} - -bool -irc_server_join(struct irc_server *s, const char *name, const char *pass) -{ - assert(s); - assert(name); - - struct irc_server_channel *ch; - bool ret = true; - - /* - * Search if there is already a channel pending or joined. If the - * server is connected we send a join command otherwise we put it there - * and wait for connection. - */ - if (!(ch = irc_server_find(s, name))) - ch = add_channel(s, name, pass, false); - - if (!ch->joined && s->state == IRC_SERVER_STATE_CONNECTED) { - if (pass) - ret = irc_server_send(s, "JOIN %s %s", name, pass); - else - ret = irc_server_send(s, "JOIN %s", name); - } - - return ret; -} - -bool -irc_server_part(struct irc_server *s, const char *name, const char *reason) -{ - assert(s); - assert(name); - - bool ret; - - if (reason && strlen(reason) > 0) - ret = irc_server_send(s, "PART %s :%s", name, reason); - else - ret = irc_server_send(s, "PART %s", name); - - return ret; -} - -bool -irc_server_topic(struct irc_server *s, const char *name, const char *topic) -{ - assert(s); - assert(name); - assert(topic); - - return irc_server_send(s, "TOPIC %s :%s", name, topic); -} - -bool -irc_server_message(struct irc_server *s, const char *chan, const char *msg) -{ - assert(s); - assert(chan); - assert(msg); - - return irc_server_send(s, "PRIVMSG %s :%s", chan, msg); -} - -bool -irc_server_me(struct irc_server *s, const char *chan, const char *message) -{ - assert(s); - assert(chan); - assert(message); - - return irc_server_send(s, "PRIVMSG %s :\001ACTION %s\001", chan, message); -} - -void -irc_server_finish(struct irc_server *s) -{ - assert(s); - - clear(s); - free(s->channels); - memset(s, 0, sizeof (*s)); -} diff -r ffe985308567 -r 7b74df7e8913 irccd/server.h --- a/irccd/server.h Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -/* - * server.h -- an IRC server - * - * Copyright (c) 2013-2021 David Demelier - * - * 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_SERVER_H -#define IRCCD_SERVER_H - -#include -#include - -#if defined(IRC_HAVE_SSL) -# include -#endif - -#include "limits.h" - -struct pollfd; - -struct irc_event; - -struct irc_server_channel { - char name[IRC_CHANNEL_MAX]; - char password[IRC_PASSWORD_MAX]; - bool joined; -}; - -enum irc_server_state { - IRC_SERVER_STATE_DISCONNECTED, - IRC_SERVER_STATE_CONNECTING, - IRC_SERVER_STATE_HANDSHAKING, - IRC_SERVER_STATE_CONNECTED, - IRC_SERVER_STATE_WAITING, - IRC_SERVER_STATE_NUM -}; - -enum irc_server_flags { - IRC_SERVER_FLAGS_SSL = (1 << 0), - IRC_SERVER_FLAGS_AUTO_REJOIN = (1 << 1) -}; - -struct irc_server_prefix { - char mode; - char token; -}; - -#if defined(IRCCD_HAVE_SSL) - -enum irc_server_ssl_state { - IRC_SERVER_SSL_NONE, - IRC_SERVER_SSL_NEED_READ, - IRC_SERVER_SSL_NEED_WRITE, -}; - -#endif - -struct irc_server { - /* Connection settings. */ - char name[IRC_NAME_MAX]; - char host[IRC_HOST_MAX]; - unsigned short port; - enum irc_server_flags flags; - - /* IRC identity. */ - char nickname[IRC_NICKNAME_MAX]; - char username[IRC_USERNAME_MAX]; - char realname[IRC_REALNAME_MAX]; - char ctcpversion[IRC_CTCPVERSION_MAX]; - char usermodes[IRC_USERMODES_MAX]; - - /* Joined channels. */ - struct irc_server_channel *channels; - size_t channelsz; - - /* Network connectivity. */ - int fd; - struct addrinfo *ai; - struct addrinfo *aip; - char in[IRC_BUF_MAX]; - char out[IRC_BUF_MAX]; - enum irc_server_state state; - - /* OpenSSL support. */ -#if defined(IRCCD_HAVE_SSL) - SSL_CTX *ctx; - SSL *ssl; - enum irc_server_ssl_state ssl_state; -#endif - - /* IRC server settings. */ - char chantypes[8]; - struct irc_server_prefix prefixes[16]; -}; - -void -irc_server_connect(struct irc_server *); - -void -irc_server_disconnect(struct irc_server *); - -void -irc_server_prepare(const struct irc_server *, struct pollfd *); - -void -irc_server_flush(struct irc_server *, const struct pollfd *); - -bool -irc_server_poll(struct irc_server *, struct irc_event *); - -struct irc_server_channel * -irc_server_find(struct irc_server *, const char *); - -bool -irc_server_send(struct irc_server *, const char *, ...); - -bool -irc_server_join(struct irc_server *, const char *, const char *); - -bool -irc_server_part(struct irc_server *, const char *, const char *); - -bool -irc_server_topic(struct irc_server *, const char *, const char *); - -bool -irc_server_message(struct irc_server *, const char *, const char *); - -bool -irc_server_me(struct irc_server *, const char *, const char *); - -void -irc_server_finish(struct irc_server *); - -#endif /* !IRCCD_SERVER_H */ diff -r ffe985308567 -r 7b74df7e8913 irccd/subst.c --- a/irccd/subst.c Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,485 +0,0 @@ -/* - * subst.c -- pattern substitution - * - * Copyright (c) 2013-2021 David Demelier - * - * 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 -#include -#include -#include -#include -#include - -#include "subst.h" - -struct pair { - const char *key; - const char *value; -}; - -struct attributes { - char fg[16]; - char bg[16]; - char attrs[4][16]; - size_t attrsz; -}; - -static const struct pair irc_colors[] = { - { "white", "0" }, - { "black", "1" }, - { "blue", "2" }, - { "green", "3" }, - { "red", "4" }, - { "brown", "5" }, - { "purple", "6" }, - { "orange", "7" }, - { "yellow", "8" }, - { "lightgreen", "9" }, - { "cyan", "10" }, - { "lightcyan", "11" }, - { "lightblue", "12" }, - { "pink", "13" }, - { "grey", "14" }, - { "lightgrey", "15" }, - { NULL, NULL } -}; - -static const struct pair irc_attrs[] = { - { "bold", "\x02" }, - { "italic", "\x09" }, - { "reverse", "\x16" }, - { "strike", "\x13" }, - { "underline", "\x15" }, - { "underline2", "\x1f" }, - { NULL, NULL } -}; - -static const struct pair shell_fg[] = { - { "black", "30" }, - { "red", "31" }, - { "green", "32" }, - { "orange", "33" }, - { "blue", "34" }, - { "purple", "35" }, - { "cyan", "36" }, - { "white", "37" }, - { "default", "39" }, - { NULL, NULL } -}; - -static const struct pair shell_bg[] = { - { "black", "40" }, - { "red", "41" }, - { "green", "42" }, - { "orange", "43" }, - { "blue", "44" }, - { "purple", "45" }, - { "cyan", "46" }, - { "white", "47" }, - { "default", "49" }, - { NULL, NULL } -}; - -static const struct pair shell_attrs[] = { - { "bold", "1" }, - { "dim", "2" }, - { "underline", "4" }, - { "blink", "5" }, - { "reverse", "7" }, - { "hidden", "8" }, - { NULL, NULL } -}; - -static inline bool -is_reserved(char token) -{ - return token == '#' || token == '@' || token == '$' || token == '!'; -} - -static inline bool -scat(char **out, size_t *outsz, const char *value) -{ - size_t written; - - if ((written = strlcpy(*out, value, *outsz)) >= *outsz) { - errno = ENOMEM; - return false; - } - - *out += written; - *outsz -= written; - - return true; -} - -static inline bool -ccat(char **out, size_t *outsz, char c) -{ - if (*outsz == 0) - return false; - - *(*out)++ = c; - *(outsz) -= 1; - - return true; -} - -static inline void -attributes_parse(const char *key, struct attributes *attrs) -{ - char attributes[64] = {0}; - - memset(attrs, 0, sizeof (*attrs)); - sscanf(key, "%15[^,],%15[^,],%63s", attrs->fg, attrs->bg, attributes); - - for (char *attr = attributes; *attr; ) { - char *p = strchr(attr, ','); - - if (p) - *p = 0; - - strlcpy(attrs->attrs[attrs->attrsz++], attr, sizeof (attrs->attrs[0])); - - if (p) - attr = p + 1; - else - *attr = '\0'; - } -} - -static inline const char * -find(const struct pair *pairs, const char *key) -{ - for (const struct pair *pair = pairs; pair->key; ++pair) - if (strcmp(pair->key, key) == 0) - return pair->value; - - return NULL; -} - -static bool -subst_date(char *out, size_t outsz, const char *input, const struct irc_subst *subst) -{ - struct tm *tm; - - if (!(subst->flags & IRC_SUBST_DATE)) - return true; - - tm = localtime(&subst->time); - - if (strftime(out, outsz, input, tm) == 0) { - errno = ENOMEM; - return false; - } - - return true; -} - -static bool -subst_keyword(const char *key, char **out, size_t *outsz, const struct irc_subst *subst) -{ - const char *value = NULL; - - for (size_t i = 0; i < subst->keywordsz; ++i) { - if (strcmp(subst->keywords[i].key, key) == 0) { - value = subst->keywords[i].value; - break; - } - } - - if (!value) - return true; - - return scat(out, outsz, value); -} - -static bool -subst_env(const char *key, char **out, size_t *outsz) -{ - const char *value; - - if (!(value = getenv(key))) - return true; - - return scat(out, outsz, value); -} - -static bool -subst_shell(const char *key, char **out, size_t *outsz) -{ - FILE *fp; - size_t written; - - /* Accept silently. */ - if (!(fp = popen(key, "r"))) - return true; - - /* - * Since we cannot determine the number of bytes that must be read, read until the end of - * the output string and cut at the number of bytes read if lesser. - */ - if ((written = fread(*out, 1, *outsz - 1, fp)) > 0) { - /* Remove '\r\n' */ - char *end; - - if ((end = memchr(*out, '\r', written)) || (end = memchr(*out, '\n', written))) - *end = '\0'; - else - end = *out + written; - - *outsz -= end - *out; - *out = end; - } - - pclose(fp); - - return true; -} - -static bool -subst_irc_attrs(const char *key, char **out, size_t *outsz) -{ - const char *value; - struct attributes attrs; - - if (!key[0]) - return ccat(out, outsz, '\x03'); - - attributes_parse(key, &attrs); - - if (attrs.fg[0] || attrs.attrs[0]) { - if (!ccat(out, outsz, '\x03')) - return false; - - /* Foreground. */ - if ((value = find(irc_colors, attrs.fg)) && !scat(out, outsz, value)) - return false; - - /* Background. */ - if (attrs.bg[0]) { - if (!ccat(out, outsz, ',')) - return false; - if ((value = find(irc_colors, attrs.bg)) && !scat(out, outsz, value)) - return false; - } - - /* Attributes. */ - for (size_t i = 0; i < attrs.attrsz; ++i) - if ((value = find(irc_attrs, attrs.attrs[i])) && !scat(out, outsz, value)) - return false; - } - - return true; -} - -static bool -subst_shell_attrs(char *key, char **out, size_t *outsz) -{ - const char *value; - struct attributes attrs; - - /* Empty attributes means reset: @{}. */ - if (!key[0]) - return scat(out, outsz, "\033[0m"); - - attributes_parse(key, &attrs); - - if (!scat(out, outsz, "\033[")) - return false; - - /* Attributes first. */ - for (size_t i = 0; i < attrs.attrsz; ++i) { - if ((value = find(shell_attrs, attrs.attrs[i])) && !scat(out, outsz, value)) - return false; - - /* Need to append ; if we have still more attributes or colors next. */ - if ((i < attrs.attrsz || attrs.fg[0] || attrs.bg[0]) && !ccat(out, outsz, ';')) - return false; - } - - /* Foreground. */ - if (attrs.fg[0]) { - if ((value = find(shell_fg, attrs.fg)) && !scat(out, outsz, value)) - return false; - if (attrs.bg[0] && !ccat(out, outsz, ';')) - return false; - } - - /* Background. */ - if (attrs.bg[0]) { - if ((value = find(shell_bg, attrs.bg)) && !scat(out, outsz, value)) - return false; - } - - return ccat(out, outsz, 'm'); -} - -static bool -subst_default(const char **p, char **out, size_t *outsz, const char *key) -{ - return ccat(out, outsz, (*p)[-2]) && - ccat(out, outsz, '{') && - scat(out, outsz, key) && - ccat(out, outsz, '}'); -} - -static bool -substitute(const char **p, char **out, size_t *outsz, const struct irc_subst *subst) -{ - char key[64] = {0}; - size_t keysz; - char *end; - bool replaced = true; - - if (!**p) - return true; - - /* Find end of construction. */ - if (!(end = strchr(*p, '}'))) { - errno = EINVAL; - return false; - } - - /* Copy key. */ - if ((keysz = end - *p) >= sizeof (key)) { - errno = ENOMEM; - return false; - } - - memcpy(key, *p, keysz); - - switch ((*p)[-2]) { - case '@': - /* attributes */ - if (subst->flags & IRC_SUBST_IRC_ATTRS) { - if (!subst_irc_attrs(key, out, outsz)) - return false; - } else if (subst->flags & IRC_SUBST_SHELL_ATTRS) { - if (!subst_shell_attrs(key, out, outsz)) - return false; - } else - replaced = false; - break; - case '#': - /* keyword */ - if (subst->flags & IRC_SUBST_KEYWORDS) { - if (!subst_keyword(key, out, outsz, subst)) - return false; - } else - replaced = false; - break; - case '$': - /* environment variable */ - if (subst->flags & IRC_SUBST_ENV) { - if (!subst_env(key, out, outsz)) - return false; - } else - replaced = false; - break; - case '!': - /* shell */ - if (subst->flags & IRC_SUBST_SHELL) { - if (!subst_shell(key, out, outsz)) - return false; - } else - replaced = false; - break; - default: - break; - } - - /* If substitution was disabled, put the token verbatim. */ - if (!replaced && !subst_default(p, out, outsz, key)) - return false; - - /* Move after '}' */ - *p = end + 1; - - return true; -} - -ssize_t -irc_subst(char *out, size_t outsz, const char *input, const struct irc_subst *subst) -{ - assert(out); - assert(subst); - - char *o = out; - - if (!outsz) - return true; - - /* Always start with the date first. */ - if (!subst_date(out, outsz, input, subst)) - goto err; - - for (const char *i = input; *i && outsz; ) { - /* - * Check if this is a reserved character, if it isn't go to the next character to - * see if it's valid otherwise we print it as last token. - * - * Example: - * "#{abc}" -> keyword sequence - * "abc #" -> keyword sequence interrupted, kept as-is. - */ - if (!is_reserved(*i)) { - if (!ccat(&o, &outsz, *i++)) - goto err; - continue; - } - - /* - * Test if after the reserved token we have the opening { construct. If it's the - * case we start substitution. - * - * Otherwise depending on what's after: - * If it is the same reserved token, it is "escaped" and printed - * If it is something else, we print the token and skip iteration. - * - * Examples: - * ## => # - * #@ => #@ - * ##{foo} => #{foo} - * #{foo} => value - */ - if (*++i == '{') { - /* Skip '{'. */ - ++i; - - if (!substitute(&i, &o, &outsz, subst)) - goto err; - } else { - if (*i == i[-1]) - ++i; - if (!ccat(&o, &outsz, i[-1])) - goto err; - } - } - - if (outsz < 1) { - errno = ENOMEM; - goto err; - } - - *o = '\0'; - - return o - out; - -err: - out[0] = '\0'; - - return -1; -} diff -r ffe985308567 -r 7b74df7e8913 irccd/subst.h --- a/irccd/subst.h Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* - * subst.h -- pattern substitution - * - * Copyright (c) 2013-2021 David Demelier - * - * 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_SUBST_H -#define IRCCD_SUBST_H - -#include -#include -#include - -enum irc_subst_flags { - IRC_SUBST_DATE = (1 << 0), - IRC_SUBST_KEYWORDS = (1 << 1), - IRC_SUBST_ENV = (1 << 2), - IRC_SUBST_SHELL = (1 << 3), - IRC_SUBST_IRC_ATTRS = (1 << 4), - IRC_SUBST_SHELL_ATTRS = (1 << 5) -}; - -struct irc_subst_keyword { - const char *key; - const char *value; -}; - -struct irc_subst { - time_t time; - enum irc_subst_flags flags; - const struct irc_subst_keyword *keywords; - size_t keywordsz; -}; - -ssize_t -irc_subst(char *, size_t, const char *, const struct irc_subst *); - -#endif /* !IRCCD_SUBST_H */ diff -r ffe985308567 -r 7b74df7e8913 irccd/util.c --- a/irccd/util.c Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -/* - * util.c -- miscellaneous utilities - * - * Copyright (c) 2013-2021 David Demelier - * - * 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 -#include -#include -#include -#include - -#include - -#include "util.h" - -void * -irc_util_malloc(size_t size) -{ - void *ret; - - if (!(ret = malloc(size))) - err(1, "malloc"); - - return ret; -} - -void * -irc_util_calloc(size_t n, size_t size) -{ - void *ret; - - if (!(ret = calloc(n, size))) - err(1, "calloc"); - - return ret; -} - -void * -irc_util_realloc(void *ptr, size_t size) -{ - void *ret; - - if (!(ret = realloc(ptr, size)) && ptr) - err(1, "realloc"); - - return ret; -} - -void * -irc_util_reallocarray(void *ptr, size_t n, size_t size) -{ - void *ret; - - if (!(ret = reallocarray(ptr, n, size))) - err(1, "reallocarray"); - - return ret; -} - -void * -irc_util_memdup(const void *ptr, size_t size) -{ - void *ret; - - if (!(ret = malloc(size))) - err(1, "malloc"); - - return memcpy(ret, ptr, size); -} - -char * -irc_util_strdup(const char *src) -{ - char *ret; - - if (!(ret = strdup(src))) - err(1, "strdup"); - - return ret; -} - -char * -irc_util_basename(const char *str) -{ - static char ret[PATH_MAX]; - char tmp[PATH_MAX]; - - strlcpy(tmp, str, sizeof (tmp)); - strlcpy(ret, basename(tmp), sizeof (ret)); - - return ret; -} - -char * -irc_util_dirname(const char *str) -{ - static char ret[PATH_MAX]; - char tmp[PATH_MAX]; - - strlcpy(tmp, str, sizeof (tmp)); - strlcpy(ret, dirname(tmp), sizeof (ret)); - - return ret; -} diff -r ffe985308567 -r 7b74df7e8913 irccd/util.h --- a/irccd/util.h Mon Jan 11 10:28:49 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* - * util.h -- miscellaneous utilities - * - * Copyright (c) 2013-2021 David Demelier - * - * 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_UTIL_H -#define IRCCD_UTIL_H - -#include - -#define IRC_UTIL_SIZE(x) (sizeof (x) / sizeof (x[0])) - -void * -irc_util_malloc(size_t); - -void * -irc_util_calloc(size_t, size_t); - -void * -irc_util_realloc(void *, size_t); - -void * -irc_util_reallocarray(void *, size_t, size_t); - -void * -irc_util_memdup(const void *, size_t); - -char * -irc_util_strdup(const char *); - -char * -irc_util_basename(const char *); - -char * -irc_util_dirname(const char *); - -#endif /* !IRCCD_UTIL_H */ diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/dl-plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/dl-plugin.c Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,220 @@ +/* + * dl-plugin.c -- native C plugins for irccd + * + * Copyright (c) 2013-2021 David Demelier + * + * 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 +#include +#include +#include +#include +#include + +#include "dl-plugin.h" +#include "log.h" +#include "plugin.h" +#include "util.h" + +#define INVOKE(pg, name, sig, ...) \ +do { \ + struct self *self = pg->data; \ + sig fn; \ + \ + if (self->handle && (fn = dlsym(self->handle, symbol(self, name)))) \ + return fn(__VA_ARGS__); \ +} while (0) + +struct self { + char prefix[32]; + void *handle; +}; + +typedef const char * (*get_option_fn)(const char *); +typedef const char * (*get_path_fn)(const char *); +typedef const char * (*get_template_fn)(const char *); +typedef const char ** (*get_options_fn)(void); +typedef const char ** (*get_paths_fn)(void); +typedef const char ** (*get_templates_fn)(void); +typedef void (*event_fn)(const struct irc_event *); +typedef void (*load_fn)(void); +typedef void (*reload_fn)(void); +typedef void (*unload_fn)(void); +typedef void (*set_option_fn)(const char *, const char *); +typedef void (*set_path_fn)(const char *, const char *); +typedef void (*set_template_fn)(const char *, const char *); + +static const char * +symbol(const struct self *self, const char *func) +{ + static char sym[128]; + + snprintf(sym, sizeof (sym), "%s_%s",self->prefix, func); + + return sym; +} + +static void +set_template(struct irc_plugin *plg, const char *key, const char *value) +{ + INVOKE(plg, "set_template", set_template_fn, key, value); +} + +static const char * +get_template(struct irc_plugin *plg, const char *key) +{ + INVOKE(plg, "get_template", get_template_fn, key); + + return NULL; +} + +static const char ** +get_templates(struct irc_plugin *plg) +{ + INVOKE(plg, "get_templates", get_templates_fn); + + return NULL; +} + +static void +set_path(struct irc_plugin *plg, const char *key, const char *value) +{ + INVOKE(plg, "set_path", set_path_fn, key, value); +} + +static const char * +get_path(struct irc_plugin *plg, const char *key) +{ + INVOKE(plg, "get_path", get_path_fn, key); + + return NULL; +} + +static const char ** +get_paths(struct irc_plugin *plg) +{ + INVOKE(plg, "get_paths", get_paths_fn); + + return NULL; +} + +static void +set_option(struct irc_plugin *plg, const char *key, const char *value) +{ + INVOKE(plg, "set_option", set_option_fn, key, value); +} + +static const char * +get_option(struct irc_plugin *plg, const char *key) +{ + INVOKE(plg, "get_option", get_option_fn, key); + + return NULL; +} + +static const char ** +get_options(struct irc_plugin *plg) +{ + INVOKE(plg, "get_options", get_options_fn); + + return NULL; +} + +static void +load(struct irc_plugin *plg) +{ + INVOKE(plg, "load", load_fn); +} + +static void +reload(struct irc_plugin *plg) +{ + INVOKE(plg, "reload", reload_fn); +} + +static void +unload(struct irc_plugin *plg) +{ + INVOKE(plg, "unload", unload_fn); +} + +static void +handle(struct irc_plugin *plg, const struct irc_event *ev) +{ + INVOKE(plg, "event", event_fn, ev); +} + +static void +finish(struct irc_plugin *plg) +{ + struct self *self = plg->data; + + if (self->handle) + dlclose(self->handle); + + memset(self, 0, sizeof (*self)); +} + +static bool +init(struct self *self, const char *path) +{ + memset(self, 0, sizeof (*self)); + + if (!(self->handle = dlopen(path, RTLD_NOW))) { + irc_log_warn("plugin: %s: %s", strerror(errno)); + return false; + } + + /* Compute prefix name */ + strlcpy(self->prefix, irc_util_basename(path), sizeof (self->prefix)); + + /* Remove plugin extension. */ + self->prefix[strcspn(self->prefix, ".")] = '\0'; + + /* Remove every invalid identifiers. */ + for (char *p = self->prefix; *p; ++p) + if (!isalnum(*p)) + *p = '_'; + + return true; +} + +bool +irc_dl_plugin_open(struct irc_plugin *plg, const char *path) +{ + struct self self; + + if (!init(&self, path)) + return false; + + /* Data and all callbacks. */ + plg->data = irc_util_memdup(&self, sizeof (self)); + plg->set_template = set_template; + plg->get_template = get_template; + plg->get_templates = get_templates; + plg->set_path = set_path; + plg->get_path = get_path; + plg->get_paths = get_paths; + plg->set_option = set_option; + plg->get_option = get_option; + plg->get_options = get_options; + plg->load = load; + plg->reload = reload; + plg->unload = unload; + plg->handle = handle; + plg->finish = finish; + + return true; +} diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/dl-plugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/dl-plugin.h Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,41 @@ +/* + * dl-plugin.c -- native C plugins for irccd + * + * Copyright (c) 2013-2021 David Demelier + * + * 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_DL_PLUGIN_H +#define IRCCD_DL_PLUGIN_H + +#include + +struct irc_plugin; + +#if defined(_WIN32) +# define IRC_DL_EXPORT __declspec(dllexport) +#else +# define IRC_DL_EXPORT +#endif + +#if defined(__APPLE__) +# define IRC_DL_EXT ".dylib" +#else +# define IRC_DL_EXT ".so" +#endif + +bool +irc_dl_plugin_open(struct irc_plugin *, const char *); + +#endif /* !IRCCD_DL_PLUGIN_H */ diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/event.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/event.h Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,131 @@ +/* + * event.h -- IRC event + * + * Copyright (c) 2013-2021 David Demelier + * + * 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_EVENT_H +#define IRCCD_EVENT_H + +#include + +#include "limits.h" + +struct irc_server; + +enum irc_event_type { + IRC_EVENT_UNKNOWN, + IRC_EVENT_CONNECT, + IRC_EVENT_DISCONNECT, + IRC_EVENT_INVITE, + IRC_EVENT_JOIN, + IRC_EVENT_KICK, + IRC_EVENT_ME, + IRC_EVENT_MESSAGE, + IRC_EVENT_MODE, + IRC_EVENT_NAMES, + IRC_EVENT_NICK, + IRC_EVENT_NOTICE, + IRC_EVENT_PART, + IRC_EVENT_TOPIC, + IRC_EVENT_WHOIS +}; + +struct irc_event { + enum irc_event_type type; + struct irc_server *server; + + /* + * Raw arguments. + * [0]: prefix + */ + 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 { + 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; + }; +}; + +#endif /* !IRCCD_EVENT_H */ diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/limits.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/limits.h Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,41 @@ +/* + * limits.h -- irccd limits + * + * Copyright (c) 2013-2021 David Demelier + * + * 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_LIMITS_H +#define IRCCD_LIMITS_H + +/* IRC limits. */ +#define IRC_NICKNAME_MAX 32 +#define IRC_USERNAME_MAX 32 +#define IRC_REALNAME_MAX 64 +#define IRC_CHANNEL_MAX 64 +#define IRC_PASSWORD_MAX 64 +#define IRC_CTCPVERSION_MAX 128 +#define IRC_USERMODES_MAX 16 + +#define IRC_MESSAGE_MAX 512 +#define IRC_ARGS_MAX 16 + +/* Network limits. */ +#define IRC_HOST_MAX 32 +#define IRC_BUF_MAX 8192 + +/* Types limits. */ +#define IRC_NAME_MAX 16 + +#endif /* !IRCCD_LIMITS_H */ diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/log.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/log.c Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,194 @@ +/* + * log.c -- loggers + * + * Copyright (c) 2013-2021 David Demelier + * + * 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 +#include +#include +#include +#include +#include + +#include "log.h" + +enum level { + LEVEL_INFO, + LEVEL_WARN, + LEVEL_DEBUG +}; + +static FILE *out, *err; +static bool verbosity; + +static void +handler_files(enum level level, const char *fmt, va_list ap) +{ + switch (level) { + case LEVEL_WARN: + vfprintf(err, fmt, ap); + putc('\n', err); + fflush(err); + break; + default: + vfprintf(out, fmt, ap); + putc('\n', out); + fflush(out); + break; + } +} + +static void +finalizer_files(void) +{ + if (out) + fclose(out); + if (err) + fclose(err); +} + +static void +handler_syslog(enum level level, const char *fmt, va_list ap) +{ + static const int table[] = { + [LEVEL_INFO] = LOG_INFO, + [LEVEL_WARN] = LOG_WARNING, + [LEVEL_DEBUG] = LOG_DEBUG + }; + + /* TODO: add compatibility shim for vsyslog. */ + vsyslog(table[level], fmt, ap); +} + +static void +finalizer_syslog(void) +{ + closelog(); +} + +static void (*handler)(enum level, const char *, va_list); +static void (*finalizer)(void); + +void +irc_log_to_syslog(void) +{ + irc_log_finish(); + + openlog("irccd", 0, LOG_DAEMON); + + handler = handler_syslog; + finalizer = finalizer_syslog; +} + +void +irc_log_to_console(void) +{ + irc_log_finish(); + + out = stdout; + err = stderr; + + handler = handler_files; + finalizer = NULL; +} + +void +irc_log_to_files(const char *stdout, const char *stderr) +{ + irc_log_finish(); + + if (!(out = fopen(stdout, "a"))) + irc_log_warn("%s: %s", stdout, strerror(errno)); + if (!(err = fopen(stderr, "a"))) + irc_log_warn("%s: %s", stdout, strerror(errno)); + + handler = handler_files; + finalizer = finalizer_files; +} + +void +irc_log_to_null(void) +{ + irc_log_finish(); + + handler = NULL; + finalizer = NULL; +} + +void +irc_log_set_verbose(bool mode) +{ + verbosity = mode; +} + +void +irc_log_info(const char *fmt, ...) +{ + assert(fmt); + + va_list ap; + + va_start(ap, fmt); + + if (verbosity && handler) + handler(LEVEL_INFO, fmt, ap); + + va_end(ap); +} + +void +irc_log_warn(const char *fmt, ...) +{ + assert(fmt); + + va_list ap; + + va_start(ap, fmt); + + if (handler) + handler(LEVEL_WARN, fmt, ap); + + va_end(ap); +} + +void +irc_log_debug(const char *fmt, ...) +{ +#if !defined(NDBEUG) + assert(fmt); + + va_list ap; + + va_start(ap, fmt); + + if (handler) + handler(LEVEL_DEBUG, fmt, ap); + + va_end(ap); +#else + (void)fmt; +#endif +} + +void +irc_log_finish(void) +{ + if (finalizer) + finalizer(); + + handler = NULL; + finalizer = NULL; +} diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/log.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/log.h Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,51 @@ +/* + * log.h -- loggers + * + * Copyright (c) 2013-2021 David Demelier + * + * 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_LOG_H +#define IRCCD_LOG_H + +#include + +void +irc_log_to_syslog(void); + +void +irc_log_to_console(void); + +void +irc_log_to_files(const char *, const char *); + +void +irc_log_to_null(void); + +void +irc_log_set_verbose(bool); + +void +irc_log_info(const char *, ...); + +void +irc_log_warn(const char *, ...); + +void +irc_log_debug(const char *, ...); + +void +irc_log_finish(void); + +#endif /* !IRCCD_LOG_H */ diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/plugin.c Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,170 @@ +/* + * plugin.c -- abstract plugin + * + * Copyright (c) 2013-2021 David Demelier + * + * 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 +#include + +#include "plugin.h" + +void +irc_plugin_set_template(struct irc_plugin *plg, const char *key, const char *value) +{ + assert(plg); + assert(key); + assert(value); + + if (plg->set_template) + plg->set_template(plg, key, value); +} + +const char * +irc_plugin_get_template(struct irc_plugin *plg, const char *key) +{ + assert(plg); + assert(key); + + if (plg->get_template) + return plg->get_template(plg, key); + + return NULL; +} + +const char ** +irc_plugin_get_templates(struct irc_plugin *plg) +{ + assert(plg); + + if (plg->get_templates) + return plg->get_templates(plg); + + return NULL; +} + +void +irc_plugin_set_path(struct irc_plugin *plg, const char *key, const char *value) +{ + assert(plg); + assert(key); + assert(value); + + if (plg->set_path) + plg->set_path(plg, key, value); +} + +const char * +irc_plugin_get_path(struct irc_plugin *plg, const char *key) +{ + assert(plg); + assert(key); + + if (plg->get_path) + return plg->get_path(plg, key); + + return NULL; +} + +const char ** +irc_plugin_get_paths(struct irc_plugin *plg) +{ + assert(plg); + + if (plg->get_paths) + return plg->get_paths(plg); + + return NULL; +} + +void +irc_plugin_set_option(struct irc_plugin *plg, const char *key, const char *value) +{ + assert(plg); + assert(key); + assert(value); + + if (plg->set_option) + plg->set_option(plg, key, value); +} + +const char * +irc_plugin_get_option(struct irc_plugin *plg, const char *key) +{ + assert(plg); + assert(key); + + if (plg->get_option) + return plg->get_option(plg, key); + + return NULL; +} + +const char ** +irc_plugin_get_options(struct irc_plugin *plg) +{ + assert(plg); + + if (plg->get_options) + return plg->get_options(plg); + + return NULL; +} + +void +irc_plugin_load(struct irc_plugin *plg) +{ + assert(plg); + + if (plg->load) + plg->load(plg); +} + +void +irc_plugin_reload(struct irc_plugin *plg) +{ + assert(plg); + + if (plg->reload) + plg->reload(plg); +} + +void +irc_plugin_unload(struct irc_plugin *plg) +{ + assert(plg); + + if (plg->unload) + plg->unload(plg); +} + +void +irc_plugin_handle(struct irc_plugin *plg, const struct irc_event *ev) +{ + assert(plg); + assert(ev); + + if (plg->handle) + plg->handle(plg, ev); +} + +void +irc_plugin_finish(struct irc_plugin *plg) +{ + assert(plg); + + if (plg->finish) + plg->finish(plg); +} diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/plugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/plugin.h Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,96 @@ +/* + * plugin.h -- abstract plugin + * + * Copyright (c) 2013-2021 David Demelier + * + * 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_PLUGIN_H +#define IRCCD_PLUGIN_H + +#include + +struct irc_event; + +struct irc_plugin { + const char *name; + const char *license; + const char *version; + const char *author; + const char *description; + void *data; + + void (*set_template)(struct irc_plugin *, const char *, const char *); + const char *(*get_template)(struct irc_plugin *, const char *); + const char **(*get_templates)(struct irc_plugin *); + + void (*set_path)(struct irc_plugin *, const char *, const char *); + const char *(*get_path)(struct irc_plugin *, const char *); + const char **(*get_paths)(struct irc_plugin *); + + void (*set_option)(struct irc_plugin *, const char *, const char *); + const char *(*get_option)(struct irc_plugin *, const char *); + const char **(*get_options)(struct irc_plugin *); + + void (*load)(struct irc_plugin *); + void (*reload)(struct irc_plugin *); + void (*unload)(struct irc_plugin *); + void (*handle)(struct irc_plugin *, const struct irc_event *); + + void (*finish)(struct irc_plugin *); +}; + +void +irc_plugin_set_template(struct irc_plugin *, const char *, const char *); + +const char * +irc_plugin_get_template(struct irc_plugin *, const char *); + +const char ** +irc_plugin_get_templates(struct irc_plugin *); + +void +irc_plugin_set_path(struct irc_plugin *, const char *, const char *); + +const char * +irc_plugin_get_path(struct irc_plugin *, const char *); + +const char ** +irc_plugin_get_paths(struct irc_plugin *); + +void +irc_plugin_set_option(struct irc_plugin *, const char *, const char *); + +const char * +irc_plugin_get_option(struct irc_plugin *, const char *); + +const char ** +irc_plugin_get_options(struct irc_plugin *); + +void +irc_plugin_load(struct irc_plugin *); + +void +irc_plugin_reload(struct irc_plugin *); + +void +irc_plugin_unload(struct irc_plugin *); + +void +irc_plugin_handle(struct irc_plugin *, const struct irc_event *); + +void +irc_plugin_finish(struct irc_plugin *); + +#endif /* !IRCCD_PLUGIN_H */ diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/server.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/server.c Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,955 @@ +/* + * server.c -- an IRC server + * + * Copyright (c) 2013-2021 David Demelier + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(IRCCD_HAVE_SSL) +# include +#endif + +#include "event.h" +#include "log.h" +#include "server.h" +#include "util.h" + +struct origin { + char nickname[IRC_NICKNAME_MAX]; + char username[IRC_USERNAME_MAX]; + 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 +compare_chan(const void *d1, const void *d2) +{ + return strcmp( + ((const struct irc_server_channel *)d1)->name, + ((const struct irc_server_channel *)d2)->name + ); +} + +static const struct origin * +parse_origin(const char *prefix) +{ + static struct origin origin; + char fmt[128]; + + memset(&origin, 0, sizeof (origin)); + snprintf(fmt, sizeof (fmt), "%%%zu[^!]!%%%zu[^@]@%%%zus", + sizeof (origin.nickname) - 1, + sizeof (origin.username) - 1, + sizeof (origin.host) - 1); + sscanf(prefix, fmt, origin.nickname, origin.username, origin.host); + + return &origin; +} + +static inline void +sort(struct irc_server *s) +{ + qsort(s->channels, s->channelsz, sizeof (*s->channels), compare_chan); +} + +static struct irc_server_channel * +add_channel(struct irc_server *s, const char *name, const char *password, bool joined) +{ + struct irc_server_channel ch = { + .joined = joined + }; + + strlcpy(ch.name, name, sizeof (ch.name)); + + if (password) + strlcpy(ch.password, password, sizeof (ch.password)); + + s->channels = irc_util_reallocarray(s->channels, ++s->channelsz, sizeof (ch)); + + memcpy(&s->channels[s->channelsz - 1], &ch, sizeof (ch)); + sort(s); + + return irc_server_find(s, name); +} + +static void +remove_channel(struct irc_server *s, struct irc_server_channel *ch) +{ + /* Null channel name will be moved at the end. */ + memset(ch, 0, sizeof (*ch)); + + s->channels = irc_util_reallocarray(s->channels, --s->channelsz, sizeof (*ch)); + sort(s); +} + +static void +read_support_prefix(struct irc_server *s, const char *value) +{ + char modes[16 + 1] = { 0 }; + char tokens[16 + 1] = { 0 }; + + if (sscanf(value, "(%16[^)])%16s", modes, tokens) == 2) { + char *pm = modes; + char *tk = tokens; + + for (size_t i = 0; i < 16 && *pm && *tk; ++i) { + s->prefixes[i].mode = *pm++; + s->prefixes[i].token = *tk++; + } + } +} + +static void +read_support_chantypes(struct irc_server *s, const char *value) +{ + strlcpy(s->chantypes, value, sizeof (s->chantypes)); +} + +static void +convert_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); +} + +static void +convert_support(struct irc_server *s, struct irc_event *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) + continue; + + if (strcmp(key, "PREFIX") == 0) + read_support_prefix(s, value); + if (strcmp(key, "CHANTYPES") == 0) + read_support_chantypes(s, value); + } +} + +static void +convert_join(struct irc_server *s, struct irc_event *ev) +{ + const struct origin *origin = parse_origin(ev->args[0]); + struct irc_server_channel *ch; + + ev->type = IRC_EVENT_JOIN; + ev->server = s; + ev->join.origin = ev->args[0]; + ev->join.channel = ev->args[2]; + + /* Also add a channel if the bot joined. */ + if (strcmp(s->nickname, origin->nickname)) { + if ((ch = irc_server_find(s, ev->args[2]))) + ch->joined = true; + else + add_channel(s, ev->args[2], NULL, true); + } +} + +static void +convert_kick(struct irc_server *s, struct irc_event *ev) +{ + 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]; + + /* + * 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) { + struct irc_server_channel *ch = irc_server_find(s, ev->args[2]); + + if (ch) { + ch->joined = false; + + if (s->flags & IRC_SERVER_FLAGS_AUTO_REJOIN) + irc_server_join(s, ch->name, ch->password); + } + } +} + +static void +convert_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]); + } + +#if 0 + if (strcmp(m->args[0], s->nickname) == 0) { + /* Own user modes. */ + strlcpy(s->usermodes, m->args[1], sizeof (s->usermodes); + } else { + /* TODO: channel modes. */ + } +#endif +} + +static void +convert_part(struct irc_server *s, struct irc_event *ev) +{ + const struct origin *origin = parse_origin(ev->args[0]); + struct irc_server_channel *ch = irc_server_find(s, ev->args[2]); + + 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) +{ + 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]; + + /* + * Detect CTCP commands which are PRIVMSG with a special boundaries. + * + * Example: + * PRIVMSG jean :\001ACTION I'm eating\001. + */ + if (is_ctcp(ev->args[3])) { + ev->type = IRC_EVENT_ME; + ev->message.message = ctcp(ev->args[3] + 1); + } +} + +static void +convert_nick(struct irc_server *s, struct irc_event *ev) +{ + const struct origin *origin = parse_origin(ev->args[0]); + + /* Update nickname if it is myself. */ + if (strcmp(origin->nickname, s->nickname) == 0) + strlcpy(s->nickname, ev->args[2], 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]; +} + +static void +convert_topic(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]; +} + +static void +convert_ping(struct irc_server *s, struct irc_event *ev) +{ + irc_server_send(s, "PONG %s", ev->args[0]); +} + +static void +convert_names(struct irc_server *s, struct irc_event *ev) +{ + (void)s; + (void)ev; +#if 0 + struct irc_server_channel *chan; + char *p, *n; + + if (m->argsz < 3 || !(chan = irc_server_find(s, m->args[2]))) + return; + + /* + * Message arguments are as following: + * 0------- 1 2------- 3-------------------- + * yourself = #channel nick1 nick2 nick3 ... + */ + for (p = m->args[3]; p; p = n ? n + 1 : NULL) { + if ((n = strpbrk(p, " "))) + *n = 0; + + channel_add(chan, s, p); + } +#endif +} + +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 }, + { "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 } +}; + +static int +compare_converter(const void *d1, const void *d2) +{ + return strcmp(d1, ((const struct convert *)d2)->command); +} + +static void +convert(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)); + + if (c) + c->convert(s, ev); +} + +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++; + + /* Skip optional spaces. */ + while (*p && isspace(*p)) + ++p; + + if (i >= IRC_MESSAGE_MAX) + return false; + + *line = p; + ev->argsz++; + + return true; +} + +static void +parse(struct irc_server *s, struct irc_event *ev, const char *line) +{ + if (!*line || *line++ != ':') + return; + if (!scan(ev, &line)) /* Prefix */ + return; + if (!scan(ev, &line)) /* Command */ + return; + + /* 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); +} + +static void +clear(struct irc_server *s) +{ + s->state = IRC_SERVER_STATE_DISCONNECTED; + + if (s->fd != 0) { + close(s->fd); + s->fd = 0; + } + + if (s->ai) { + freeaddrinfo(s->ai); + s->ai = NULL; + s->aip = NULL; + } + +#if defined(IRCCD_HAVE_SSL) + if (s->ssl) { + SSL_free(s->ssl); + s->ssl = NULL; + } + if (s->ctx) { + SSL_CTX_free(s->ctx); + s->ctx = NULL; + } +#endif +} + +static bool +lookup(struct irc_server *s) +{ + struct addrinfo hints = { + .ai_socktype = SOCK_STREAM, + }; + char service[16]; + int ret; + + snprintf(service, sizeof (service), "%hu", s->port); + + if ((ret = getaddrinfo(s->host, service, &hints, &s->ai)) != 0) + irc_log_warn("server %s: %s", s->name, gai_strerror(ret)); + + s->aip = s->ai; + + return true; +} + +static void +auth(struct irc_server *s) +{ + s->state = IRC_SERVER_STATE_CONNECTED; + + 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_HAVE_SSL) + +static void +secure_update(struct irc_server *s, int ret) +{ + (void)s; + (void)ret; + + assert(s); + + int r; + + if (!(s->flags & SERVER_FL_SSL)) + return; + + switch ((r = SSL_get_error(s->ssl, ret))) { + case SSL_ERROR_WANT_READ: + s->ssl_state = SERVER_SSL_NEED_READ; + break; + case SSL_ERROR_WANT_WRITE: + s->ssl_state = SERVER_SSL_NEED_WRITE; + break; + case SSL_ERROR_SSL: + clear(s); + break; + default: + s->ssl_state = SERVER_SSL_NONE; + break; + } +} + +#endif + +static void +handshake(struct irc_server *s) +{ + assert(s); + + if (!(s->flags & IRC_SERVER_FLAGS_SSL)) + auth(s); + else { +#if defined(IRCCD_HAVE_SSL) + int r; + + s->state = SERVER_ST_HANDSHAKING; + + if ((r = SSL_do_handshake(s->ssl)) > 0) + auth(s); + + secure_update(s, r); +#endif + } +} + +static void +secure_connect(struct irc_server *s) +{ + assert(s); + + if (!(s->flags & IRC_SERVER_FLAGS_SSL)) + handshake(s); + else { +#if defined(IRCCD_HAVE_SSL) + int r; + + if (!s->ctx) + s->ctx = SSL_CTX_new(TLS_method()); + if (!s->ssl) { + s->ssl = SSL_new(s->ctx); + SSL_set_fd(s->ssl, s->fd); + } + + if ((r = SSL_connect(s->ssl)) > 0) + ssl_handshake(s); + + secure_update(s, r); +#endif + } +} + +static void +dial(struct irc_server *s) +{ + /* No more address available. */ + if (s->aip == NULL) { + clear(s); + return; + } + + for (; s->aip; s->aip = s->aip->ai_next) { + /* We may need to close a socket that was open earlier. */ + if (s->fd != 0) + close(s->fd); + + s->fd = socket(s->aip->ai_family, s->aip->ai_socktype, + s->aip->ai_protocol); + + if (s->fd < 0) { + irc_log_warn("server %s: %s", s->name, strerror(errno)); + continue; + } + + /* TODO: is F_GETFL required before? */ + fcntl(s->fd, F_SETFL, O_NONBLOCK); + + /* + * With some luck, the connection completes immediately, + * otherwise we will need to wait until the socket is writable. + */ + if (connect(s->fd, s->aip->ai_addr, s->aip->ai_addrlen) == 0) { + secure_connect(s); + break; + } + + /* Connect failed, check why. */ + switch (errno) { + case EINPROGRESS: + case EAGAIN: + /* Let the writable state to determine. */ + return; + default: + irc_log_warn("server %s: %s", s->name, strerror(errno)); + break; + } + } +} + +static void +input(struct irc_server *s) +{ + char buf[IRC_MESSAGE_MAX] = {0}; + ssize_t nr = 0; + + if (s->flags & IRC_SERVER_FLAGS_SSL) { +#if defined(IRCCD_HAVE_SSL) + nr = SSL_read(s->ssl, buf, sizeof (buf) - 1); + secure_update(s, nr); +#endif + } else { + if ((nr = recv(s->fd, buf, sizeof (buf) - 1, 0)) < 0) + clear(s); + } + + if (nr > 0) { + if (strlcat(s->in, buf, sizeof (s->in)) >= sizeof (s->in)) { + irc_log_warn("server %s: input buffer too long", s->name); + clear(s); + } + } +} + +static void +output(struct irc_server *s) +{ + ssize_t ns = 0; + + if (s->flags & IRC_SERVER_FLAGS_SSL) { +#if defined(IRCCD_HAVE_SSL) + ns = SSL_write(s->ssl, s->out.data, s->out.size); + secure_update(s, ns); +#endif + } else if ((ns = send(s->fd, s->out, strlen(s->out), 0)) <= 0) + clear(s); + + if (ns > 0) { + /* Optimize if everything was sent. */ + if ((size_t)ns >= sizeof (s->out)) + s->out[0] = '\0'; + else + memmove(s->out, s->out + ns, sizeof (s->out) - ns); + } +} + +static void +prepare_connecting(const struct irc_server *s, struct pollfd *pfd) +{ + (void)s; + +#if defined(IRCCD_HAVE_SSL) + if (s->flags & IRC_SERVER_FLAGS_SSL && s->ssl && s->ctx) { + switch (s->ssl_state) { + case IRC_SERVER_SSL_NEED_READ: + pfd->events |= POLLIN; + break; + case IRC_SERVER_SSL_NEED_WRITE: + pfd->events |= POLLOUT; + break; + default: + break; + } + } else +#endif + pfd->events |= POLLOUT; +} + +static void +prepare_ready(const struct irc_server *s, struct pollfd *pfd) +{ +#if defined(IRCCD_HAVE_SSL) + if (s->flags & IRC_SERVER_FLAGS_SSL && s->ssl_state) { + switch (s->ssl_state) { + case SERVER_SSL_NEED_READ: + pfd->events |= POLLIN; + break; + case SERVER_SSL_NEED_WRITE: + pfd->events |= POLLOUT; + break; + default: + break; + } + } else { +#endif + pfd->events |= POLLIN; + + if (s->out[0]) + pfd->events |= POLLOUT; +#if defined(IRCCD_HAVE_SSL) + } +#endif +} + +static void +flush_connecting(struct irc_server *s, const struct pollfd *pfd) +{ + (void)pfd; + + int res, err = -1; + socklen_t len = sizeof (int); + + if ((res = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &err, &len)) < 0 || err) { + irc_log_warn("server %s: %s", s->name, strerror(res ? err : errno)); + dial(s); + } else + secure_connect(s); +} + +static void +flush_handshaking(struct irc_server *s, const struct pollfd *pfd) +{ + (void)pfd; + + handshake(s); +} + +static void +flush_ready(struct irc_server *s, const struct pollfd *pfd) +{ + if (pfd->revents & POLLERR || pfd->revents & POLLHUP) + clear(s); + if (pfd->revents & POLLIN) + input(s); + if (pfd->revents & POLLOUT) + output(s); +} + +static const struct { + void (*prepare)(const struct irc_server *, struct pollfd *); + void (*flush)(struct irc_server *, const struct pollfd *); +} io_table[] = { + [IRC_SERVER_STATE_CONNECTING] = { + prepare_connecting, + flush_connecting + }, + [IRC_SERVER_STATE_HANDSHAKING] = { + prepare_ready, + flush_handshaking + }, + [IRC_SERVER_STATE_CONNECTED] = { + prepare_ready, + flush_ready + }, +}; + +void +irc_server_connect(struct irc_server *s) +{ + assert(s); + + s->state = IRC_SERVER_STATE_CONNECTING; + + if (!lookup(s)) + clear(s); + else + dial(s); +} + +void +irc_server_disconnect(struct irc_server *s) +{ + assert(s); + + clear(s); +} + +void +irc_server_prepare(const struct irc_server *s, struct pollfd *pfd) +{ + pfd->fd = s->fd; + pfd->events = 0; + + if (io_table[s->state].prepare) + io_table[s->state].prepare(s, pfd); +} + +void +irc_server_flush(struct irc_server *s, const struct pollfd *pfd) +{ + if (io_table[s->state].flush) + io_table[s->state].flush(s, pfd); +} + +bool +irc_server_poll(struct irc_server *s, struct irc_event *ev) +{ + assert(s); + assert(ev); + + char *pos; + size_t length; + + if (!(pos = strstr(s->in, "\r\n"))) + return false; + + /* Turn end of the string at delimiter. */ + *pos = 0; + length = pos - s->in; + + /* Clear event in case we don't understand this message. */ + memset(ev, 0, sizeof (*ev)); + ev->type = IRC_EVENT_UNKNOWN; + + if (length > 0) + parse(s, ev, s->in); + + memmove(s->in, pos + 2, sizeof (s->in) - (length + 2)); + + return true; +} + +struct irc_server_channel * +irc_server_find(struct irc_server *s, const char *name) +{ + assert(s); + assert(name); + + struct irc_server_channel key = {0}; + + strlcpy(key.name, name, sizeof (key.name)); + + return bsearch(&key, s->channels, s->channelsz, sizeof (key), compare_chan); +} + +bool +irc_server_send(struct irc_server *s, const char *fmt, ...) +{ + assert(s); + assert(fmt); + + char buf[sizeof (s->out)]; + va_list ap; + size_t len, avail, required; + + va_start(ap, fmt); + required = vsnprintf(buf, sizeof (buf), fmt, ap); + va_end(ap); + + len = strlen(s->out); + avail = sizeof (s->out) - len; + + /* Don't forget \r\n. */ + if (required + 2 >= avail) + return false; + + strlcat(s->out, buf, sizeof (s->out)); + strlcat(s->out, "\r\n", sizeof (s->out)); + + return true; +} + +bool +irc_server_join(struct irc_server *s, const char *name, const char *pass) +{ + assert(s); + assert(name); + + struct irc_server_channel *ch; + bool ret = true; + + /* + * Search if there is already a channel pending or joined. If the + * server is connected we send a join command otherwise we put it there + * and wait for connection. + */ + if (!(ch = irc_server_find(s, name))) + ch = add_channel(s, name, pass, false); + + if (!ch->joined && s->state == IRC_SERVER_STATE_CONNECTED) { + if (pass) + ret = irc_server_send(s, "JOIN %s %s", name, pass); + else + ret = irc_server_send(s, "JOIN %s", name); + } + + return ret; +} + +bool +irc_server_part(struct irc_server *s, const char *name, const char *reason) +{ + assert(s); + assert(name); + + bool ret; + + if (reason && strlen(reason) > 0) + ret = irc_server_send(s, "PART %s :%s", name, reason); + else + ret = irc_server_send(s, "PART %s", name); + + return ret; +} + +bool +irc_server_topic(struct irc_server *s, const char *name, const char *topic) +{ + assert(s); + assert(name); + assert(topic); + + return irc_server_send(s, "TOPIC %s :%s", name, topic); +} + +bool +irc_server_message(struct irc_server *s, const char *chan, const char *msg) +{ + assert(s); + assert(chan); + assert(msg); + + return irc_server_send(s, "PRIVMSG %s :%s", chan, msg); +} + +bool +irc_server_me(struct irc_server *s, const char *chan, const char *message) +{ + assert(s); + assert(chan); + assert(message); + + return irc_server_send(s, "PRIVMSG %s :\001ACTION %s\001", chan, message); +} + +void +irc_server_finish(struct irc_server *s) +{ + assert(s); + + clear(s); + free(s->channels); + memset(s, 0, sizeof (*s)); +} diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/server.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/server.h Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,147 @@ +/* + * server.h -- an IRC server + * + * Copyright (c) 2013-2021 David Demelier + * + * 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_SERVER_H +#define IRCCD_SERVER_H + +#include +#include + +#if defined(IRC_HAVE_SSL) +# include +#endif + +#include "limits.h" + +struct pollfd; + +struct irc_event; + +struct irc_server_channel { + char name[IRC_CHANNEL_MAX]; + char password[IRC_PASSWORD_MAX]; + bool joined; +}; + +enum irc_server_state { + IRC_SERVER_STATE_DISCONNECTED, + IRC_SERVER_STATE_CONNECTING, + IRC_SERVER_STATE_HANDSHAKING, + IRC_SERVER_STATE_CONNECTED, + IRC_SERVER_STATE_WAITING, + IRC_SERVER_STATE_NUM +}; + +enum irc_server_flags { + IRC_SERVER_FLAGS_SSL = (1 << 0), + IRC_SERVER_FLAGS_AUTO_REJOIN = (1 << 1) +}; + +struct irc_server_prefix { + char mode; + char token; +}; + +#if defined(IRCCD_HAVE_SSL) + +enum irc_server_ssl_state { + IRC_SERVER_SSL_NONE, + IRC_SERVER_SSL_NEED_READ, + IRC_SERVER_SSL_NEED_WRITE, +}; + +#endif + +struct irc_server { + /* Connection settings. */ + char name[IRC_NAME_MAX]; + char host[IRC_HOST_MAX]; + unsigned short port; + enum irc_server_flags flags; + + /* IRC identity. */ + char nickname[IRC_NICKNAME_MAX]; + char username[IRC_USERNAME_MAX]; + char realname[IRC_REALNAME_MAX]; + char ctcpversion[IRC_CTCPVERSION_MAX]; + char usermodes[IRC_USERMODES_MAX]; + + /* Joined channels. */ + struct irc_server_channel *channels; + size_t channelsz; + + /* Network connectivity. */ + int fd; + struct addrinfo *ai; + struct addrinfo *aip; + char in[IRC_BUF_MAX]; + char out[IRC_BUF_MAX]; + enum irc_server_state state; + + /* OpenSSL support. */ +#if defined(IRCCD_HAVE_SSL) + SSL_CTX *ctx; + SSL *ssl; + enum irc_server_ssl_state ssl_state; +#endif + + /* IRC server settings. */ + char chantypes[8]; + struct irc_server_prefix prefixes[16]; +}; + +void +irc_server_connect(struct irc_server *); + +void +irc_server_disconnect(struct irc_server *); + +void +irc_server_prepare(const struct irc_server *, struct pollfd *); + +void +irc_server_flush(struct irc_server *, const struct pollfd *); + +bool +irc_server_poll(struct irc_server *, struct irc_event *); + +struct irc_server_channel * +irc_server_find(struct irc_server *, const char *); + +bool +irc_server_send(struct irc_server *, const char *, ...); + +bool +irc_server_join(struct irc_server *, const char *, const char *); + +bool +irc_server_part(struct irc_server *, const char *, const char *); + +bool +irc_server_topic(struct irc_server *, const char *, const char *); + +bool +irc_server_message(struct irc_server *, const char *, const char *); + +bool +irc_server_me(struct irc_server *, const char *, const char *); + +void +irc_server_finish(struct irc_server *); + +#endif /* !IRCCD_SERVER_H */ diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/subst.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/subst.c Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,485 @@ +/* + * subst.c -- pattern substitution + * + * Copyright (c) 2013-2021 David Demelier + * + * 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 +#include +#include +#include +#include +#include + +#include "subst.h" + +struct pair { + const char *key; + const char *value; +}; + +struct attributes { + char fg[16]; + char bg[16]; + char attrs[4][16]; + size_t attrsz; +}; + +static const struct pair irc_colors[] = { + { "white", "0" }, + { "black", "1" }, + { "blue", "2" }, + { "green", "3" }, + { "red", "4" }, + { "brown", "5" }, + { "purple", "6" }, + { "orange", "7" }, + { "yellow", "8" }, + { "lightgreen", "9" }, + { "cyan", "10" }, + { "lightcyan", "11" }, + { "lightblue", "12" }, + { "pink", "13" }, + { "grey", "14" }, + { "lightgrey", "15" }, + { NULL, NULL } +}; + +static const struct pair irc_attrs[] = { + { "bold", "\x02" }, + { "italic", "\x09" }, + { "reverse", "\x16" }, + { "strike", "\x13" }, + { "underline", "\x15" }, + { "underline2", "\x1f" }, + { NULL, NULL } +}; + +static const struct pair shell_fg[] = { + { "black", "30" }, + { "red", "31" }, + { "green", "32" }, + { "orange", "33" }, + { "blue", "34" }, + { "purple", "35" }, + { "cyan", "36" }, + { "white", "37" }, + { "default", "39" }, + { NULL, NULL } +}; + +static const struct pair shell_bg[] = { + { "black", "40" }, + { "red", "41" }, + { "green", "42" }, + { "orange", "43" }, + { "blue", "44" }, + { "purple", "45" }, + { "cyan", "46" }, + { "white", "47" }, + { "default", "49" }, + { NULL, NULL } +}; + +static const struct pair shell_attrs[] = { + { "bold", "1" }, + { "dim", "2" }, + { "underline", "4" }, + { "blink", "5" }, + { "reverse", "7" }, + { "hidden", "8" }, + { NULL, NULL } +}; + +static inline bool +is_reserved(char token) +{ + return token == '#' || token == '@' || token == '$' || token == '!'; +} + +static inline bool +scat(char **out, size_t *outsz, const char *value) +{ + size_t written; + + if ((written = strlcpy(*out, value, *outsz)) >= *outsz) { + errno = ENOMEM; + return false; + } + + *out += written; + *outsz -= written; + + return true; +} + +static inline bool +ccat(char **out, size_t *outsz, char c) +{ + if (*outsz == 0) + return false; + + *(*out)++ = c; + *(outsz) -= 1; + + return true; +} + +static inline void +attributes_parse(const char *key, struct attributes *attrs) +{ + char attributes[64] = {0}; + + memset(attrs, 0, sizeof (*attrs)); + sscanf(key, "%15[^,],%15[^,],%63s", attrs->fg, attrs->bg, attributes); + + for (char *attr = attributes; *attr; ) { + char *p = strchr(attr, ','); + + if (p) + *p = 0; + + strlcpy(attrs->attrs[attrs->attrsz++], attr, sizeof (attrs->attrs[0])); + + if (p) + attr = p + 1; + else + *attr = '\0'; + } +} + +static inline const char * +find(const struct pair *pairs, const char *key) +{ + for (const struct pair *pair = pairs; pair->key; ++pair) + if (strcmp(pair->key, key) == 0) + return pair->value; + + return NULL; +} + +static bool +subst_date(char *out, size_t outsz, const char *input, const struct irc_subst *subst) +{ + struct tm *tm; + + if (!(subst->flags & IRC_SUBST_DATE)) + return true; + + tm = localtime(&subst->time); + + if (strftime(out, outsz, input, tm) == 0) { + errno = ENOMEM; + return false; + } + + return true; +} + +static bool +subst_keyword(const char *key, char **out, size_t *outsz, const struct irc_subst *subst) +{ + const char *value = NULL; + + for (size_t i = 0; i < subst->keywordsz; ++i) { + if (strcmp(subst->keywords[i].key, key) == 0) { + value = subst->keywords[i].value; + break; + } + } + + if (!value) + return true; + + return scat(out, outsz, value); +} + +static bool +subst_env(const char *key, char **out, size_t *outsz) +{ + const char *value; + + if (!(value = getenv(key))) + return true; + + return scat(out, outsz, value); +} + +static bool +subst_shell(const char *key, char **out, size_t *outsz) +{ + FILE *fp; + size_t written; + + /* Accept silently. */ + if (!(fp = popen(key, "r"))) + return true; + + /* + * Since we cannot determine the number of bytes that must be read, read until the end of + * the output string and cut at the number of bytes read if lesser. + */ + if ((written = fread(*out, 1, *outsz - 1, fp)) > 0) { + /* Remove '\r\n' */ + char *end; + + if ((end = memchr(*out, '\r', written)) || (end = memchr(*out, '\n', written))) + *end = '\0'; + else + end = *out + written; + + *outsz -= end - *out; + *out = end; + } + + pclose(fp); + + return true; +} + +static bool +subst_irc_attrs(const char *key, char **out, size_t *outsz) +{ + const char *value; + struct attributes attrs; + + if (!key[0]) + return ccat(out, outsz, '\x03'); + + attributes_parse(key, &attrs); + + if (attrs.fg[0] || attrs.attrs[0]) { + if (!ccat(out, outsz, '\x03')) + return false; + + /* Foreground. */ + if ((value = find(irc_colors, attrs.fg)) && !scat(out, outsz, value)) + return false; + + /* Background. */ + if (attrs.bg[0]) { + if (!ccat(out, outsz, ',')) + return false; + if ((value = find(irc_colors, attrs.bg)) && !scat(out, outsz, value)) + return false; + } + + /* Attributes. */ + for (size_t i = 0; i < attrs.attrsz; ++i) + if ((value = find(irc_attrs, attrs.attrs[i])) && !scat(out, outsz, value)) + return false; + } + + return true; +} + +static bool +subst_shell_attrs(char *key, char **out, size_t *outsz) +{ + const char *value; + struct attributes attrs; + + /* Empty attributes means reset: @{}. */ + if (!key[0]) + return scat(out, outsz, "\033[0m"); + + attributes_parse(key, &attrs); + + if (!scat(out, outsz, "\033[")) + return false; + + /* Attributes first. */ + for (size_t i = 0; i < attrs.attrsz; ++i) { + if ((value = find(shell_attrs, attrs.attrs[i])) && !scat(out, outsz, value)) + return false; + + /* Need to append ; if we have still more attributes or colors next. */ + if ((i < attrs.attrsz || attrs.fg[0] || attrs.bg[0]) && !ccat(out, outsz, ';')) + return false; + } + + /* Foreground. */ + if (attrs.fg[0]) { + if ((value = find(shell_fg, attrs.fg)) && !scat(out, outsz, value)) + return false; + if (attrs.bg[0] && !ccat(out, outsz, ';')) + return false; + } + + /* Background. */ + if (attrs.bg[0]) { + if ((value = find(shell_bg, attrs.bg)) && !scat(out, outsz, value)) + return false; + } + + return ccat(out, outsz, 'm'); +} + +static bool +subst_default(const char **p, char **out, size_t *outsz, const char *key) +{ + return ccat(out, outsz, (*p)[-2]) && + ccat(out, outsz, '{') && + scat(out, outsz, key) && + ccat(out, outsz, '}'); +} + +static bool +substitute(const char **p, char **out, size_t *outsz, const struct irc_subst *subst) +{ + char key[64] = {0}; + size_t keysz; + char *end; + bool replaced = true; + + if (!**p) + return true; + + /* Find end of construction. */ + if (!(end = strchr(*p, '}'))) { + errno = EINVAL; + return false; + } + + /* Copy key. */ + if ((keysz = end - *p) >= sizeof (key)) { + errno = ENOMEM; + return false; + } + + memcpy(key, *p, keysz); + + switch ((*p)[-2]) { + case '@': + /* attributes */ + if (subst->flags & IRC_SUBST_IRC_ATTRS) { + if (!subst_irc_attrs(key, out, outsz)) + return false; + } else if (subst->flags & IRC_SUBST_SHELL_ATTRS) { + if (!subst_shell_attrs(key, out, outsz)) + return false; + } else + replaced = false; + break; + case '#': + /* keyword */ + if (subst->flags & IRC_SUBST_KEYWORDS) { + if (!subst_keyword(key, out, outsz, subst)) + return false; + } else + replaced = false; + break; + case '$': + /* environment variable */ + if (subst->flags & IRC_SUBST_ENV) { + if (!subst_env(key, out, outsz)) + return false; + } else + replaced = false; + break; + case '!': + /* shell */ + if (subst->flags & IRC_SUBST_SHELL) { + if (!subst_shell(key, out, outsz)) + return false; + } else + replaced = false; + break; + default: + break; + } + + /* If substitution was disabled, put the token verbatim. */ + if (!replaced && !subst_default(p, out, outsz, key)) + return false; + + /* Move after '}' */ + *p = end + 1; + + return true; +} + +ssize_t +irc_subst(char *out, size_t outsz, const char *input, const struct irc_subst *subst) +{ + assert(out); + assert(subst); + + char *o = out; + + if (!outsz) + return true; + + /* Always start with the date first. */ + if (!subst_date(out, outsz, input, subst)) + goto err; + + for (const char *i = input; *i && outsz; ) { + /* + * Check if this is a reserved character, if it isn't go to the next character to + * see if it's valid otherwise we print it as last token. + * + * Example: + * "#{abc}" -> keyword sequence + * "abc #" -> keyword sequence interrupted, kept as-is. + */ + if (!is_reserved(*i)) { + if (!ccat(&o, &outsz, *i++)) + goto err; + continue; + } + + /* + * Test if after the reserved token we have the opening { construct. If it's the + * case we start substitution. + * + * Otherwise depending on what's after: + * If it is the same reserved token, it is "escaped" and printed + * If it is something else, we print the token and skip iteration. + * + * Examples: + * ## => # + * #@ => #@ + * ##{foo} => #{foo} + * #{foo} => value + */ + if (*++i == '{') { + /* Skip '{'. */ + ++i; + + if (!substitute(&i, &o, &outsz, subst)) + goto err; + } else { + if (*i == i[-1]) + ++i; + if (!ccat(&o, &outsz, i[-1])) + goto err; + } + } + + if (outsz < 1) { + errno = ENOMEM; + goto err; + } + + *o = '\0'; + + return o - out; + +err: + out[0] = '\0'; + + return -1; +} diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/subst.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/subst.h Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,50 @@ +/* + * subst.h -- pattern substitution + * + * Copyright (c) 2013-2021 David Demelier + * + * 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_SUBST_H +#define IRCCD_SUBST_H + +#include +#include +#include + +enum irc_subst_flags { + IRC_SUBST_DATE = (1 << 0), + IRC_SUBST_KEYWORDS = (1 << 1), + IRC_SUBST_ENV = (1 << 2), + IRC_SUBST_SHELL = (1 << 3), + IRC_SUBST_IRC_ATTRS = (1 << 4), + IRC_SUBST_SHELL_ATTRS = (1 << 5) +}; + +struct irc_subst_keyword { + const char *key; + const char *value; +}; + +struct irc_subst { + time_t time; + enum irc_subst_flags flags; + const struct irc_subst_keyword *keywords; + size_t keywordsz; +}; + +ssize_t +irc_subst(char *, size_t, const char *, const struct irc_subst *); + +#endif /* !IRCCD_SUBST_H */ diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/util.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/util.c Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,117 @@ +/* + * util.c -- miscellaneous utilities + * + * Copyright (c) 2013-2021 David Demelier + * + * 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 +#include +#include +#include +#include + +#include + +#include "util.h" + +void * +irc_util_malloc(size_t size) +{ + void *ret; + + if (!(ret = malloc(size))) + err(1, "malloc"); + + return ret; +} + +void * +irc_util_calloc(size_t n, size_t size) +{ + void *ret; + + if (!(ret = calloc(n, size))) + err(1, "calloc"); + + return ret; +} + +void * +irc_util_realloc(void *ptr, size_t size) +{ + void *ret; + + if (!(ret = realloc(ptr, size)) && ptr) + err(1, "realloc"); + + return ret; +} + +void * +irc_util_reallocarray(void *ptr, size_t n, size_t size) +{ + void *ret; + + if (!(ret = reallocarray(ptr, n, size))) + err(1, "reallocarray"); + + return ret; +} + +void * +irc_util_memdup(const void *ptr, size_t size) +{ + void *ret; + + if (!(ret = malloc(size))) + err(1, "malloc"); + + return memcpy(ret, ptr, size); +} + +char * +irc_util_strdup(const char *src) +{ + char *ret; + + if (!(ret = strdup(src))) + err(1, "strdup"); + + return ret; +} + +char * +irc_util_basename(const char *str) +{ + static char ret[PATH_MAX]; + char tmp[PATH_MAX]; + + strlcpy(tmp, str, sizeof (tmp)); + strlcpy(ret, basename(tmp), sizeof (ret)); + + return ret; +} + +char * +irc_util_dirname(const char *str) +{ + static char ret[PATH_MAX]; + char tmp[PATH_MAX]; + + strlcpy(tmp, str, sizeof (tmp)); + strlcpy(ret, dirname(tmp), sizeof (ret)); + + return ret; +} diff -r ffe985308567 -r 7b74df7e8913 lib/irccd/util.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/util.h Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,50 @@ +/* + * util.h -- miscellaneous utilities + * + * Copyright (c) 2013-2021 David Demelier + * + * 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_UTIL_H +#define IRCCD_UTIL_H + +#include + +#define IRC_UTIL_SIZE(x) (sizeof (x) / sizeof (x[0])) + +void * +irc_util_malloc(size_t); + +void * +irc_util_calloc(size_t, size_t); + +void * +irc_util_realloc(void *, size_t); + +void * +irc_util_reallocarray(void *, size_t, size_t); + +void * +irc_util_memdup(const void *, size_t); + +char * +irc_util_strdup(const char *); + +char * +irc_util_basename(const char *); + +char * +irc_util_dirname(const char *); + +#endif /* !IRCCD_UTIL_H */ diff -r ffe985308567 -r 7b74df7e8913 tests/example-dl-plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/example-dl-plugin.c Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,160 @@ +/* + * example-dl-plugin.c -- simple plugin for unit tests + * + * Copyright (c) 2013-2021 David Demelier + * + * 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 + +#include +#include +#include + +struct kw { + const char *key; + char value[256]; +}; + +/* + * Options. + */ +static struct kw options[] = { + { "option-1", "value-1" } +}; + +static const char *options_list[] = { + "option-1", + NULL +}; + +/* + * Templates. + */ +static struct kw templates[] = { + { "template-1", "Welcome #{target}" } +}; + +static const char *templates_list[] = { + "template-1", + NULL +}; + +/* + * Paths. + */ +static struct kw paths[] = { + { "path-1", "/usr/local/etc" } +}; + +static const char *paths_list[] = { + "path-1", + NULL +}; + +static void +set(struct kw *table, size_t tablesz, const char *key, const char *value) +{ + for (size_t i = 0; i < tablesz; ++i) { + if (strcmp(table[i].key, key) == 0) { + strlcpy(table[i].value, value, sizeof (table[i].value)); + break; + } + } +} + +static const char * +get(const struct kw *table, size_t tablesz, const char *key) +{ + for (size_t i = 0; i < tablesz; ++i) + if (strcmp(table[i].key, key) == 0) + return table[i].value; + + return NULL; +} + +IRC_DL_EXPORT void +example_dl_plugin_set_option(const char *key, const char *value) +{ + set(options, IRC_UTIL_SIZE(options), key, value); +} + +IRC_DL_EXPORT const char * +example_dl_plugin_get_option(const char *key) +{ + return get(options, IRC_UTIL_SIZE(options), key); +} + +IRC_DL_EXPORT const char ** +example_dl_plugin_get_options(void) +{ + return options_list; +} + +IRC_DL_EXPORT void +example_dl_plugin_set_template(const char *key, const char *value) +{ + set(templates, IRC_UTIL_SIZE(templates), key, value); +} + +IRC_DL_EXPORT const char * +example_dl_plugin_get_template(const char *key) +{ + return get(templates, IRC_UTIL_SIZE(templates), key); +} + +IRC_DL_EXPORT const char ** +example_dl_plugin_get_templates(void) +{ + return templates_list; +} + +IRC_DL_EXPORT void +example_dl_plugin_set_path(const char *key, const char *value) +{ + set(paths, IRC_UTIL_SIZE(paths), key, value); +} + +IRC_DL_EXPORT const char * +example_dl_plugin_get_path(const char *key) +{ + return get(paths, IRC_UTIL_SIZE(paths), key); +} + +IRC_DL_EXPORT const char ** +example_dl_plugin_get_paths(void) +{ + return paths_list; +} + +IRC_DL_EXPORT void +example_dl_plugin_event(const struct irc_event *ev) +{ + (void)ev; +} + +IRC_DL_EXPORT void +example_dl_plugin_load(void) +{ +} + +IRC_DL_EXPORT void +example_dl_plugin_reload(void) +{ +} + +IRC_DL_EXPORT void +example_dl_plugin_unload(void) +{ +} diff -r ffe985308567 -r 7b74df7e8913 tests/test-dl-plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-dl-plugin.c Mon Jan 11 21:25:58 2021 +0100 @@ -0,0 +1,160 @@ +/* + * test-dl-plugin.c -- test dl-plugin.h functions + * + * Copyright (c) 2013-2021 David Demelier + * + * 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 + +#include +#include +#include + +static struct irc_plugin plugin; + +static void +setup(void *udata) +{ + (void)udata; + + /* TODO: No idea how to stop greatest from here. */ + if (!irc_dl_plugin_open(&plugin, SOURCEDIR "/tests/example-dl-plugin" IRC_DL_EXT)) + exit(1); +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(&plugin); +} + +GREATEST_TEST +options_set_get(void) +{ + irc_plugin_set_option(&plugin, "option-1", "new-value-1"); + GREATEST_ASSERT_STR_EQ("new-value-1", irc_plugin_get_option(&plugin, "option-1")); + GREATEST_ASSERT(!irc_plugin_get_option(&plugin, "not-found")); + GREATEST_PASS(); +} + +GREATEST_TEST +options_list(void) +{ + const char **options = irc_plugin_get_options(&plugin); + + GREATEST_ASSERT_STR_EQ("option-1", options[0]); + GREATEST_ASSERT(!options[1]); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_options) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(options_set_get); + GREATEST_RUN_TEST(options_list); +} + +GREATEST_TEST +paths_set_get(void) +{ + irc_plugin_set_path(&plugin, "path-1", "new-value-1"); + GREATEST_ASSERT_STR_EQ("new-value-1", irc_plugin_get_path(&plugin, "path-1")); + GREATEST_ASSERT(!irc_plugin_get_path(&plugin, "not-found")); + GREATEST_PASS(); +} + +GREATEST_TEST +paths_list(void) +{ + const char **paths = irc_plugin_get_paths(&plugin); + + GREATEST_ASSERT_STR_EQ("path-1", paths[0]); + GREATEST_ASSERT(!paths[1]); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_paths) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(paths_set_get); + GREATEST_RUN_TEST(paths_list); +} + +GREATEST_TEST +templates_set_get(void) +{ + irc_plugin_set_template(&plugin, "template-1", "new-value-1"); + GREATEST_ASSERT_STR_EQ("new-value-1", irc_plugin_get_template(&plugin, "template-1")); + GREATEST_ASSERT(!irc_plugin_get_template(&plugin, "not-found")); + GREATEST_PASS(); +} + +GREATEST_TEST +templates_list(void) +{ + const char **templates = irc_plugin_get_templates(&plugin); + + GREATEST_ASSERT_STR_EQ("template-1", templates[0]); + GREATEST_ASSERT(!templates[1]); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_templates) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(templates_set_get); + GREATEST_RUN_TEST(templates_list); +} + +GREATEST_TEST +calls_simple(void) +{ + struct irc_event ev = {0}; + + irc_plugin_load(&plugin); + irc_plugin_unload(&plugin); + irc_plugin_reload(&plugin); + irc_plugin_handle(&plugin, &ev); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_calls) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(calls_simple); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_options); + GREATEST_RUN_SUITE(suite_paths); + GREATEST_RUN_SUITE(suite_templates); + GREATEST_RUN_SUITE(suite_calls); + GREATEST_MAIN_END(); + + return 0; +}