view irccdctl/main.c @ 947:95201fd9ad88

irccd: servers are now linked lists - Add reference counting to be shared with Javascript. - Implement server-disconnect command.
author David Demelier <markand@malikania.fr>
date Sat, 16 Jan 2021 09:45:33 +0100
parents e43ccb1f0ace
children 9fcb0038fe0a
line wrap: on
line source

/*
 * 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/un.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#include <unistd.h>

#include <stdio.h>

#include <irccd/limits.h>
#include <irccd/util.h>

static bool verbose;
static int sock;
static struct sockaddr_un sockaddr = {
	.sun_family = PF_LOCAL,
	.sun_path = "/tmp/irccd.sock"
};
static char in[IRC_BUF_MAX];
static char out[IRC_BUF_MAX];

static char *
poll(void)
{
	static char ret[IRC_BUF_MAX];
	char *nl;

	while (!(nl = strstr(in, "\n"))) {
		char buf[IRC_BUF_MAX] = {0};

		if (recv(sock, buf, sizeof (buf) - 1, 0) <= 0)
			err(1, "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_MAX];
	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 void
ok(void)
{
	const char *response = poll();

	if (strcmp(response, "OK") != 0)
		errx(1, "abort: %s", response);
}

static void
cmd_server_disconnect(int argc, char **argv)
{
	if (argc == 1)
		req("SERVER-DISCONNECT %s", argv[0]);
	else
		req("SERVER-DISCONNECT");

	ok();
}

static void
cmd_server_list(int argc, char **argv)
{
	(void)argc;
	(void)argv;

	char *list;

	req("SERVER-LIST");

	if (strncmp(list = poll(), "OK ", 3) != 0)
		errx(1, "failed to retrieve server list");

	/* Skip "OK " */
	list += 3;

	/* Since list is separated by spaces, just convert them to \n */
	for (char *p; (p = strchr(list, ' ')); )
		*p = '\n';

	puts(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)
{
#if 0
	req("MODE %s %s %s%c%s%c%s%c%s", argv[0], argv[1], argv[2],
		argc >= 4 ? ' ', argv[3] : "",
		argc >= 5 ? ' ', argv[4] : "",
		argc >= 6 ? ' ', argv[5] : "");
	ok();
#endif
}

static void
cmd_server_nick(int argc, char **argv)
{
	req("SERVER-NICK %s %s", argv[0], argv[1]);
	ok();
}

static void
cmd_server_notice(int argc, char **argv)
{
	req("SERVER-NOTICE %s %s %s", argv[0], argv[1], argv[2]);
	ok();
}

static void
cmd_server_part(int argc, char **argv)
{
	/* 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)
{
	req("SERVER-TOPIC %s %s %s", argv[0], argv[1], argv[2]);
	ok();
}

static const struct cmd {
	const char *name;
	unsigned int minargs;
	unsigned int maxargs;
	void (*exec)(int, char **);
} cmds[] = {
	/* name                 min     max     exec                   */
	{ "server-disconnect",  0,      1,      cmd_server_disconnect   },
	{ "server-list",        0,      0,      cmd_server_list         },
	{ "server-me",          3,      3,      cmd_server_me           },
	{ "server-message",     3,      3,      cmd_server_message      },
	{ "server-mode",        3,      6,      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        }
};

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 (argc < c->minargs || argc > c->maxargs)
		errx(1, "abort: invalid number of arguments");

	c->exec(argc, argv);
}

static noreturn void
usage(void)
{
	/* TODO: getprogname() */
	fprintf(stderr, "usage: irccdctl [-v] [-s sock] command [arguments...]\n");
	exit(1);
}

int
main(int argc, char **argv)
{
	for (int ch; (ch = getopt(argc, argv, "s:v")) != -1; ) {
		switch (ch) {
		case 's':
			strlcpy(sockaddr.sun_path, optarg, sizeof (sockaddr.sun_path));
			break;
		case 'v':
			verbose = true;
			break;
		default:
			break;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc < 1)
		usage();

	dial();
	check();
	run(argc, argv);
}