Mercurial > irccd
diff irccdctl/irccdctl.c @ 1037:8f8ce47aba8a
make: switch to GNU make
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 27 Apr 2021 09:22:16 +0200 |
parents | |
children | c857bc879669 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/irccdctl/irccdctl.c Tue Apr 27 09:22:16 2021 +0200 @@ -0,0 +1,995 @@ +/* + * main.c -- irccdctl(1) main file + * + * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include <assert.h> +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <unistd.h> + +#include <ketopt.h> + +#include <irccd/limits.h> +#include <irccd/util.h> + +static int verbose; +static int sock; +static struct sockaddr_un sockaddr = { + .sun_family = PF_LOCAL, + .sun_path = "/tmp/irccd.sock" +}; +static char in[IRC_BUF_LEN]; +static char out[IRC_BUF_LEN]; + +static char * +poll(void) +{ + static char ret[IRC_BUF_LEN]; + char *nl; + + while (!(nl = strstr(in, "\n"))) { + char buf[IRC_BUF_LEN] = {0}; + ssize_t nr; + + if ((nr = recv(sock, buf, sizeof (buf) - 1, 0)) <= 0) + errc(1, nr == 0 ? ECONNRESET : errno, "abort"); + if (strlcat(in, buf, sizeof (in)) >= sizeof (in)) + errc(1, EMSGSIZE, "abort"); + } + + *nl = '\0'; + strlcpy(ret, in, sizeof (ret)); + memmove(in, nl + 1, sizeof (in) - (nl - in) - 1); + + return ret; +} + +static void +dial(void) +{ + const struct timeval tv = { + .tv_sec = 30 + }; + + if ((sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) + err(1, "socket"); + if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof (tv)) < 0 || + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)) < 0) + err(1, "setsockopt"); + if (connect(sock, (const struct sockaddr *)&sockaddr, SUN_LEN(&sockaddr)) < 0) + err(1, "connect"); +} + +static void +check(void) +{ + /* Ensure we're talking to irccd. */ + int major, minor, patch; + + if ((sscanf(poll(), "IRCCD %d.%d.%d", &major, &minor, &patch) != 3)) + errx(1, "abort: not irccd instance"); + if (verbose) + printf("connected to irccd %d.%d.%d\n", major, minor, patch); +} + +static void +req(const char *fmt, ...) +{ + char buf[IRC_BUF_LEN]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof (buf), fmt, ap); + va_end(ap); + + if (strlcat(out, buf, sizeof (out)) >= sizeof (out) || + strlcat(out, "\n", sizeof (out)) >= sizeof (out)) + errc(1, EMSGSIZE, "abort"); + + while (out[0]) { + ssize_t ns, len; + + len = strlen(out); + + if ((ns = send(sock, out, len, MSG_NOSIGNAL)) <= 0) + err(1, "send"); + + if (ns >= len) + memset(out, 0, sizeof (out)); + else + memmove(out, out + ns, sizeof (out) - ns); + } +} + +static char * +ok(void) +{ + char *response = poll(); + + if (strncmp(response, "OK", 2) != 0) + errx(1, "abort: %s", response); + + /* Skip "OK". */ + response += 2; + + while (*response && isspace(*response)) + response++; + + return response; +} + +static void +show_connect(char *line) +{ + const char *args[2] = {0}; + + if (irc_util_split(line, args, 2, ' ') == 2) { + printf("%-16s: %s\n", "event", "onConnect"); + printf("%-16s: %s\n", "server", args[0]); + } +} + +static void +show_disconnect(char *line) +{ + const char *args[2] = {0}; + + if (irc_util_split(line, args, 2, ' ') == 2) { + printf("%-16s: %s\n", "event", "onDisonnect"); + printf("%-16s: %s\n", "server", args[0]); + } +} + +static void +show_invite(char *line) +{ + const char *args[5] = {0}; + + if (irc_util_split(line, args, 5, ' ') == 5) { + printf("%-16s: %s\n", "event", "onInvite"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + printf("%-16s: %s\n", "nickname", args[4]); + } +} + +static void +show_join(char *line) +{ + const char *args[4] = {0}; + + if (irc_util_split(line, args, 4, ' ') == 4) { + printf("%-16s: %s\n", "event", "onJoin"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + } +} + +static void +show_kick(char *line) +{ + const char *args[6] = {0}; + + if (irc_util_split(line, args, 6, ' ') >= 5) { + printf("%-16s: %s\n", "event", "onKick"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + printf("%-16s: %s\n", "target", args[4]); + printf("%-16s: %s\n", "reason", args[5] ? args[5] : ""); + } +} + +static void +show_me(char *line) +{ + const char *args[5] = {0}; + + if (irc_util_split(line, args, 5, ' ') == 5) { + printf("%-16s: %s\n", "event", "onMe"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + printf("%-16s: %s\n", "message", args[4]); + } +} + +static void +show_message(char *line) +{ + const char *args[5] = {0}; + + if (irc_util_split(line, args, 5, ' ') == 5) { + printf("%-16s: %s\n", "event", "onMessage"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + printf("%-16s: %s\n", "message", args[4]); + } +} + +static void +show_mode(char *line) +{ + const char *args[8] = {0}; + + if (irc_util_split(line, args, 8, ' ') >= 5) { + printf("%-16s: %s\n", "event", "onMode"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + printf("%-16s: %s\n", "mode", args[4]); + printf("%-16s: %s\n", "limit", (args[5] ? args[5] : "")); + printf("%-16s: %s\n", "user", (args[6] ? args[6] : "")); + printf("%-16s: %s\n", "mask", (args[7] ? args[7] : "")); + } +} + +static void +show_nick(char *line) +{ + const char *args[4] = {0}; + + if (irc_util_split(line, args, 4, ' ') == 4) { + printf("%-16s: %s\n", "event", "onNick"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "nickname", args[3]); + } +} + +static void +show_notice(char *line) +{ + const char *args[5] = {0}; + + if (irc_util_split(line, args, 5, ' ') == 5) { + printf("%-16s: %s\n", "event", "onNotice"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + printf("%-16s: %s\n", "message", args[4]); + } +} + +static void +show_part(char *line) +{ + const char *args[5] = {0}; + + if (irc_util_split(line, args, 5, ' ') >= 4) { + printf("%-16s: %s\n", "event", "onPart"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + printf("%-16s: %s\n", "reason", (args[4] ? args[4] : "")); + } +} + +static void +show_topic(char *line) +{ + const char *args[5] = {0}; + + if (irc_util_split(line, args, 5, ' ') >= 4) { + printf("%-16s: %s\n", "event", "onTopic"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "origin", args[2]); + printf("%-16s: %s\n", "channel", args[3]); + printf("%-16s: %s\n", "topic", args[4]); + } +} + +static void +show_whois(char *line) +{ + const char *args[6] = {0}; + //char *p, *token; + + if (irc_util_split(line, args, 6, ' ') >= 4) { + printf("%-16s: %s\n", "event", "onWhois"); + printf("%-16s: %s\n", "server", args[1]); + printf("%-16s: %s\n", "nickname", args[2]); + printf("%-16s: %s\n", "username", args[3]); + printf("%-16s: %s\n", "hostname", args[4]); + printf("%-16s: %s\n", "username", args[5]); + //printf("channels: %s\n", args[6]); + } +} + +static const struct { + const char *event; + void (*show)(char *); +} watchtable[] = { + { "EVENT-CONNECT", show_connect }, + { "EVENT-DISCONNECT", show_disconnect }, + { "EVENT-INVITE", show_invite }, + { "EVENT-JOIN", show_join }, + { "EVENT-KICK", show_kick }, + { "EVENT-MESSAGE", show_message }, + { "EVENT-ME", show_me }, + { "EVENT-MODE", show_mode }, + { "EVENT-NICK", show_nick }, + { "EVENT-NOTICE", show_notice }, + { "EVENT-PART", show_part }, + { "EVENT-TOPIC", show_topic }, + { "EVENT-WHOIS", show_whois } +}; + +static void +show(char *ev) +{ + for (size_t i = 0; i < IRC_UTIL_SIZE(watchtable); ++i) { + if (strncmp(watchtable[i].event, ev, strlen(watchtable[i].event)) == 0) { + watchtable[i].show(ev); + printf("\n"); + break; + } + } +} + +static void +plugin_list_set(int argc, char **argv, const char *cmd) +{ + char *line, *p; + size_t num = 0; + + if (argc == 3) { + req("%s %s %s %s", cmd, argv[0], argv[1], argv[2]); + ok(); + return; + } + + if (argc == 2) + req("%s %s %s", cmd, argv[0], argv[1]); + else + req("%s %s", cmd, argv[0]); + + if (sscanf(line = ok(), "%zu", &num) != 1) + errx(1, "could not retrieve list"); + + if (argc == 2) + puts(poll()); + else { + while (num-- != 0 && (line = poll())) { + if (!(p = strchr(line, '='))) + continue; + + *p = '\0'; + printf("%-16s: %s\n", line, p + 1); + } + } +} + +static void +response_list(const char *cmd) +{ + char *list; + + req(cmd); + + if (strncmp(list = poll(), "OK ", 3) != 0) + errx(1, "failed to retrieve plugin list"); + + list += 3; + + for (char *p; (p = strchr(list, ' ')); ) + *p = '\n'; + + if (*list) + puts(list); +} + +static void +cmd_hook_add(int argc, char **argv) +{ + (void)argc; + + req("HOOK-ADD %s %s", argv[0], argv[1]); + ok(); +} + +static void +cmd_hook_list(int argc, char **argv) +{ + (void)argc; + (void)argv; + + response_list("HOOK-LIST"); +} + +static void +cmd_hook_remove(int argc, char **argv) +{ + (void)argc; + + req("HOOK-REMOVE %s", argv[0]); + ok(); +} + +static void +cmd_plugin_config(int argc, char **argv) +{ + return plugin_list_set(argc, argv, "PLUGIN-CONFIG"); +} + +/* + * Response: + * + * OK name + * summary + * version + * license + * author + */ +static void +cmd_plugin_info(int argc, char **argv) +{ + (void)argc; + + const char *response; + + req("PLUGIN-INFO %s", argv[0]); + + if (strncmp((response = poll()), "OK ", 3) != 0) + errx(1, "failed to retrieve plugin information"); + + printf("%-16s: %s\n", "name", response + 3); + printf("%-16s: %s\n", "summary", poll()); + printf("%-16s: %s\n", "version", poll()); + printf("%-16s: %s\n", "license", poll()); + printf("%-16s: %s\n", "author", poll()); +} + +static void +cmd_plugin_list(int argc, char **argv) +{ + (void)argc; + (void)argv; + + response_list("PLUGIN-LIST"); +} + +static void +cmd_plugin_load(int argc, char **argv) +{ + (void)argc; + + req("PLUGIN-LOAD %s", argv[0]); + ok(); +} + +static void +cmd_plugin_path(int argc, char **argv) +{ + return plugin_list_set(argc, argv, "PLUGIN-PATH"); +} + +static void +cmd_plugin_reload(int argc, char **argv) +{ + if (argc == 1) + req("PLUGIN-RELOAD %s", argv[0]); + else + req("PLUGIN-RELOAD"); + + ok(); +} + +static void +cmd_plugin_template(int argc, char **argv) +{ + return plugin_list_set(argc, argv, "PLUGIN-TEMPLATE"); +} + +static void +cmd_plugin_unload(int argc, char **argv) +{ + if (argc == 1) + req("PLUGIN-UNLOAD %s", argv[0]); + else + req("PLUGIN-UNLOAD"); + + ok(); +} + +static void +cmd_rule_add(int argc, char **argv) +{ + ketopt_t ko = KETOPT_INIT; + FILE *fp; + char out[IRC_BUF_LEN]; + + if (!(fp = fmemopen(out, sizeof (out) - 1, "w"))) + err(1, "fmemopen"); + + /* TODO: invalid option. */ + for (int ch; (ch = ketopt(&ko, argc, argv, 0, "c:e:i:o:p:s:", NULL)) != -1; ) + fprintf(fp, "%c=%s ", ch, ko.arg); + + argc -= ko.ind; + argv += ko.ind; + + if (argc < 1) + errx(1, "missing accept or drop rule action"); + + fprintf(fp, "%s", argv[0]); + + if (ferror(fp) || feof(fp)) + err(1, "fprintf"); + + fclose(fp); + req("RULE-ADD %s %s", argv[0], out); + ok(); +} + +static void +cmd_rule_edit(int argc, char **argv) +{ + ketopt_t ko = KETOPT_INIT; + FILE *fp; + char out[IRC_BUF_LEN]; + + if (!(fp = fmemopen(out, sizeof (out) - 1, "w"))) + err(1, "fmemopen"); + + /* TODO: invalid option. */ + for (int ch; (ch = ketopt(&ko, argc, argv, 0, "a:C:c:E:e:O:o:P:p:S:s:", NULL)) != -1; ) { + if (ch == 'a') + fprintf(fp, "a=%s ", ko.arg); + else + fprintf(fp, "%c%c%s ", tolower(ch), isupper(ch) ? '-' : '+', ko.arg); + } + + argc -= ko.ind; + argv += ko.ind; + + if (argc < 1) + errx(1, "missing rule index"); + + if (ferror(fp) || feof(fp)) + err(1, "fprintf"); + + fclose(fp); + req("RULE-EDIT %s %s", argv[0], out); + ok(); +} + +/* + * Response: + * + * OK <n> + * accept + * server1 server2 server3 ... + * channel1 channel2 channel3 ... + * origin1 origin2 origin3 ... + * plugin1 plugin2 plugin3 ... + * event1 event2 plugin3 ... + * (repeat for every rule in <n>) + */ +static void +cmd_rule_list(int argc, char **argv) +{ + (void)argc; + (void)argv; + + size_t num = 0; + + req("RULE-LIST"); + + if (sscanf(ok(), "%zu", &num) != 1) + errx(1, "could not retrieve rule list"); + + for (size_t i = 0; i < num; ++i) { + printf("%-16s: %zu\n", "index", i); + printf("%-16s: %s\n", "action", poll()); + printf("%-16s: %s\n", "servers", poll()); + printf("%-16s: %s\n", "channels", poll()); + printf("%-16s: %s\n", "origins", poll()); + printf("%-16s: %s\n", "plugins", poll()); + printf("%-16s: %s\n", "events", poll()); + printf("\n"); + } +} + +static void +cmd_rule_move(int argc, char **argv) +{ + (void)argc; + + long long from, to; + const char *errstr; + + if ((from = strtonum(argv[0], 0, LLONG_MAX, &errstr)) == 0 && errstr) + err(1, "%s", argv[0]); + if ((to = strtonum(argv[1], 0, LLONG_MAX, &errstr)) == 0 && errstr) + err(1, "%s", argv[1]); + + req("RULE-MOVE %lld %lld", from, to); + ok(); +} + +static void +cmd_rule_remove(int argc, char **argv) +{ + (void)argc; + + req("RULE-REMOVE %s", argv[0]); + ok(); +} + +static void +cmd_server_connect(int argc, char **argv) +{ + ketopt_t ko = KETOPT_INIT; + int ssl = 0; + const char *nickname = "irccd", + *username = "irccd", + *realname = "IRC Client Daemon", + *port = "6667"; + + for (int ch; (ch = ketopt(&ko, argc, argv, 0, "sn:r:u:p:", NULL)) != -1; ) { + switch (ch) { + case 's': + ssl = 1; + break; + case 'n': + nickname = ko.arg; + break; + case 'r': + realname = ko.arg; + break; + case 'u': + username = ko.arg; + break; + case 'p': + port = ko.arg; + break; + default: + break; + } + } + + argc -= ko.ind; + argv += ko.ind; + + if (argc < 2) + errx(1, "missing id and/or host"); + + req("SERVER-CONNECT %s %s %s%s %s %s %s", argv[0], argv[1], (ssl ? "+" : ""), + port, nickname, username, realname); + ok(); +} + +static void +cmd_server_disconnect(int argc, char **argv) +{ + if (argc == 1) + req("SERVER-DISCONNECT %s", argv[0]); + else + req("SERVER-DISCONNECT"); + + ok(); +} + +/* + * Response: + * + * OK name + * hostname port [ssl] + * nickname username realname + * chan1 chan2 chanN + */ +static void +cmd_server_info(int argc, char **argv) +{ + (void)argc; + + char *list; + const char *args[16] = {0}; + + req("SERVER-INFO %s", argv[0]); + + if (strncmp(list = poll(), "OK ", 3) != 0) + errx(1, "failed to retrieve server information"); + + printf("%-16s: %s\n", "name", list + 3); + + if (irc_util_split((list = poll()), args, 3, ' ') < 2) + errx(1, "malformed server connection"); + + printf("%-16s: %s\n", "hostname", args[0]); + printf("%-16s: %s\n", "port", args[1]); + + if (args[2]) + printf("%-16s: %s\n", "ssl", "true"); + + if (irc_util_split((list = poll()), args, 3, ' ') != 3) + errx(1, "malformed server ident"); + + printf("%-16s: %s\n", "nickname", args[0]); + printf("%-16s: %s\n", "username", args[0]); + printf("%-16s: %s\n", "realname", args[0]); + printf("%-16s: %s\n", "channels", poll()); +} + +static void +cmd_server_join(int argc, char **argv) +{ + if (argc >= 3) + req("SERVER-JOIN %s %s %s", argv[0], argv[1], argv[2]); + else + req("SERVER-JOIN %s %s", argv[0], argv[1]); + + ok(); +} + +static void +cmd_server_list(int argc, char **argv) +{ + (void)argc; + (void)argv; + + response_list("SERVER-LIST"); +} + +static void +cmd_server_message(int argc, char **argv) +{ + (void)argc; + + req("SERVER-MESSAGE %s %s %s", argv[0], argv[1], argv[2]); + ok(); +} + +static void +cmd_server_me(int argc, char **argv) +{ + (void)argc; + + req("SERVER-ME %s %s %s", argv[0], argv[1], argv[2]); + ok(); +} + +static void +cmd_server_mode(int argc, char **argv) +{ + req("SERVER-MODE %s %s %s%c%s", argv[0], argv[1], argv[2], + argc >= 4 ? ' ' : '\0', + argc >= 4 ? argv[3] : ""); + ok(); +} + +static void +cmd_server_nick(int argc, char **argv) +{ + (void)argc; + + req("SERVER-NICK %s %s", argv[0], argv[1]); + ok(); +} + +static void +cmd_server_notice(int argc, char **argv) +{ + (void)argc; + + req("SERVER-NOTICE %s %s %s", argv[0], argv[1], argv[2]); + ok(); +} + +static void +cmd_server_part(int argc, char **argv) +{ + (void)argc; + + /* Let's advertise irccd a bit. */ + req("SERVER-PART %s %s %s", argv[0], argv[1], + argc >= 3 ? argv[2] : "irccd is shutting down"); + ok(); +} + +static void +cmd_server_topic(int argc, char **argv) +{ + (void)argc; + + req("SERVER-TOPIC %s %s %s", argv[0], argv[1], argv[2]); + ok(); +} + +static void +cmd_watch(int argc, char **argv) +{ + (void)argc; + (void)argv; + + struct timeval tv = {0}; + char *ev; + + /* Enable watch. */ + req("WATCH"); + ok(); + + /* Turn off timeout to receive indefinitely. */ + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)) < 0) + err(1, "setsockopt"); + + while ((ev = poll())) + show(ev); +} + +static const struct cmd { + const char *name; + int minargs; + int maxargs; + void (*exec)(int, char **); +} cmds[] = { + /* name min max exec */ + { "hook-add", 2, 2, cmd_hook_add }, + { "hook-list", 0, 0, cmd_hook_list }, + { "hook-remove", 1, 1, cmd_hook_remove }, + { "plugin-config", 1, 3, cmd_plugin_config }, + { "plugin-info", 1, 1, cmd_plugin_info }, + { "plugin-list", 0, 0, cmd_plugin_list }, + { "plugin-load", 1, 1, cmd_plugin_load }, + { "plugin-path", 0, 3, cmd_plugin_path }, + { "plugin-reload", 0, 1, cmd_plugin_reload }, + { "plugin-template", 1, 3, cmd_plugin_template }, + { "plugin-unload", 0, 1, cmd_plugin_unload }, + { "rule-add", -1, -1, cmd_rule_add }, + { "rule-edit", -1, -1, cmd_rule_edit }, + { "rule-list", 0, 0, cmd_rule_list }, + { "rule-move", 2, 2, cmd_rule_move }, + { "rule-remove", 1, 1, cmd_rule_remove }, + { "server-connect", -1, -1, cmd_server_connect }, + { "server-disconnect", 0, 1, cmd_server_disconnect }, + { "server-info", 1, 1, cmd_server_info }, + { "server-join", 2, 3, cmd_server_join }, + { "server-list", 0, 0, cmd_server_list }, + { "server-me", 3, 3, cmd_server_me }, + { "server-message", 3, 3, cmd_server_message }, + { "server-mode", 3, 4, cmd_server_mode }, + { "server-nick", 2, 2, cmd_server_nick }, + { "server-notice", 3, 3, cmd_server_notice }, + { "server-part", 3, 3, cmd_server_part }, + { "server-topic", 3, 3, cmd_server_topic }, + { "watch", 0, 0, cmd_watch } +}; + +static int +cmp_cmd(const void *d1, const void *d2) +{ + return strcmp(d1, ((const struct cmd *)d2)->name); +} + +static const struct cmd * +find_cmd(const char *name) +{ + return bsearch(name, cmds, IRC_UTIL_SIZE(cmds), sizeof (cmds[0]), cmp_cmd); +} + +static void +run(int argc, char **argv) +{ + const struct cmd *c; + + if (!(c = find_cmd(argv[0]))) + errx(1, "abort: command not found"); + + --argc; + ++argv; + + if ((c->minargs != -1 && argc < c->minargs) || (c->minargs != -1 && argc > c->maxargs)) + errx(1, "abort: invalid number of arguments"); + + c->exec(argc, argv); +} + +noreturn static void +usage(void) +{ + fprintf(stderr, "usage: %s [-v] [-s sock] command [options...] [arguments...]\n", getprogname()); + exit(1); +} + +noreturn static void +help(void) +{ + fprintf(stderr, "usage: %s hook-add name path\n", getprogname()); + fprintf(stderr, " %s hook-list\n", getprogname()); + fprintf(stderr, " %s hook-remove id\n", getprogname()); + fprintf(stderr, " %s plugin-config id [variable [value]]\n", getprogname()); + fprintf(stderr, " %s plugin-info id\n", getprogname()); + fprintf(stderr, " %s plugin-list\n", getprogname()); + fprintf(stderr, " %s plugin-load name\n", getprogname()); + fprintf(stderr, " %s plugin-path [variable [value]]\n", getprogname()); + fprintf(stderr, " %s plugin-template [variable [value]]\n", getprogname()); + fprintf(stderr, " %s plugin-reload [plugin]\n", getprogname()); + fprintf(stderr, " %s plugin-unload [plugin]\n", getprogname()); + fprintf(stderr, " %s rule-add [-c channel] [-e event] [-i index] [-o origin] [-p plugin] [-s server] accept|drop\n", getprogname()); + fprintf(stderr, " %s rule-edit [-a accept|drop] [-c|C channel] [-e|E event] [-o|O origin] [-s|S server] index\n", getprogname()); + fprintf(stderr, " %s rule-list\n", getprogname()); + fprintf(stderr, " %s rule-move from to\n", getprogname()); + fprintf(stderr, " %s rule-remove index\n", getprogname()); + fprintf(stderr, " %s server-connect [-n nickname] [-r realname] [-u username] [-p port] id hostname\n", getprogname()); + fprintf(stderr, " %s server-disconnect [server]\n", getprogname()); + fprintf(stderr, " %s server-info server\n", getprogname()); + fprintf(stderr, " %s server-invite server target channel\n", getprogname()); + fprintf(stderr, " %s server-join server channel [password]\n", getprogname()); + fprintf(stderr, " %s server-kick server target channel [reason]\n", getprogname()); + fprintf(stderr, " %s server-list\n", getprogname()); + fprintf(stderr, " %s server-me server target message\n", getprogname()); + fprintf(stderr, " %s server-message server target message\n", getprogname()); + fprintf(stderr, " %s server-mode server target mode [args]\n", getprogname()); + fprintf(stderr, " %s server-nick server nickname\n", getprogname()); + fprintf(stderr, " %s server-notice server target message\n", getprogname()); + fprintf(stderr, " %s server-part server channel [reason]\n", getprogname()); + fprintf(stderr, " %s server-reconnect [server]\n", getprogname()); + fprintf(stderr, " %s server-topic server channel topic\n", getprogname()); + fprintf(stderr, " %s watch\n", getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + ketopt_t ko = KETOPT_INIT; + + setprogname("irccdctl"); + + --argc; + ++argv; + + for (int ch; (ch = ketopt(&ko, argc, argv, 0, "s:v", NULL)) != -1; ) { + switch (ch) { + case 's': + strlcpy(sockaddr.sun_path, ko.arg, sizeof (sockaddr.sun_path)); + break; + case 'v': + verbose = 1; + break; + default: + usage(); + break; + } + } + + argc -= ko.ind; + argv += ko.ind; + + if (argc < 1) + usage(); + else if (strcmp(argv[0], "help") == 0) + help(); + + dial(); + check(); + run(argc, argv); +}