/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2024-2025 Brett Sheffield <bacs@librecast.net> */

#define _GNU_SOURCE /* for in6_pktinfo */
#include <agent.h>
#include <log.h>
#include <state.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <key.h>
#include <librecast/net.h>
#include <limits.h>
#include <locale.h>
#include <net/if.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <y.tab.h>
#include <lex.h>

#define unused (void)

char *agent_dir_home = NULL;
char *agent_dir_cache = NULL;
char *agent_dir_config = NULL;
char *agent_dir_data = NULL;
char *agent_dir_seq = NULL;
char *agent_dir_state = NULL;
int lineno = 1;
int yyparse(state_t *state);
static volatile int g_reload;
static volatile int g_stop;

static void handle_signal(int signo, siginfo_t *info, void *context)
{
	(void)signo, (void) info, (void)context;
	switch (signo) {
		case SIGINT: g_stop++; break;
		case SIGHUP: g_reload++; break;
	}
}

static int agent_print_version(state_t *state)
{
	(void)state;
	fprintf(stderr, "%s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
	return 0;
}

static int agent_key_add(state_t *state)
{
	char *key;
	if (!(key = arg_pop(state))) {
		fprintf(stderr, "%s\n", AGENT_ERR_NOKEY);
		return (errno = EINVAL), -1;
	}
	return keyring_add(state, key);
}

static int agent_key_del(state_t *state)
{
	char *key;
	if (!(key = arg_pop(state))) {
		fprintf(stderr, "%s\n", AGENT_ERR_NOKEY);
		return (errno = EINVAL), -1;
	}
	return keyring_del(state, key);
}

static int agent_key(state_t *state)
{
	char *act;
	if (!(act = arg_pop(state))) {
		act = "show";
	}
	if (!strcmp(act, "add")) {
		return agent_key_add(state);
	}
	else if (!strcmp(act, "del")) {
		return agent_key_del(state);
	}
	else if (!strcmp(act, "show")) {
		return keyring_show(state);
	}
	return -1;
}

static int agent_sign(state_t *state)
{
	lc_ctx_t *lctx;
	lc_channel_t *chan;
	lc_keypair_t *signing_key = &state->defaults.keyring.s;
	char *bearer_key, *chanstr;
	int flags = KEY_CAP_SAVE | KEY_CAP_PATH;
	int rc = -1;
	uint8_t capbits = 0;

	if (!(bearer_key = arg_pop(state))) {
		fprintf(stderr, "%s\n", AGENT_ERR_NOKEY);
		return (errno = EINVAL), -1;
	}
	if (!(chanstr = arg_pop(state))) {
		fprintf(stderr, "%s\n", AGENT_ERR_NOCHANNEL);
		return (errno = EINVAL), -1;
	}

	/* ensure own key loaded */
	if (strlen(state->defaults.keyring.shex) < 1) return (errno = ENOKEY), -1;

	lctx = lc_ctx_new();
	if (!lctx) return -1;
	chan = lc_channel_new(lctx, chanstr);
	if (!chan) goto err_lctx_free;

	unsigned char bearer_psk[crypto_sign_PUBLICKEYBYTES] = {0};
	key_combo_hex2psk(bearer_psk, sizeof bearer_psk, bearer_key);
	rc = key_cap_issue(state, signing_key, bearer_psk, chan, capbits, state->expires, flags);

err_lctx_free:
	lc_ctx_free(lctx);
	return rc;
}

static int agent_whoami(state_t *state)
{
	/* write key to stout when not a tty so it can be redirected */
	FILE *f = (isatty(1)) ? stderr : stdout;
	fprintf(f, "%s\n", state->defaults.keyring.phex);
	return 0;
}

static int agent_load_channel_token(lc_token_t *token, state_t *state, char *chanstr)
{
	unsigned char hash[HASHSIZE];
	hash_generic(hash, sizeof hash, (unsigned char *)chanstr, strlen(chanstr));
	return key_cap_load(token, state, hash, sizeof token->channel);
}

static int agent_set_channel_key(state_t *state, lc_channel_t *chan)
{
	size_t len = 0;
	/* set encryption key, if we have one */
	if (state->defaults.seed) {
		E(STATE_VERBOSE, "generating key from seed\n");
		lc_key_t enc;
		fprintf(stderr, "generating channel key\n");
		unsigned char salt[crypto_pwhash_SALTBYTES];
		crypto_generichash(salt, sizeof salt, (unsigned char *)state->defaults.seed, len, NULL, 0);
		sodium_mlock(state->defaults.chan_key, sizeof state->defaults.chan_key);
		enc.key = state->defaults.chan_key;
		enc.keylen = sizeof state->defaults.chan_key;
		lc_channel_setkey(chan, &enc, LC_CODE_SYMM);

	}
	return 0;
}

static int agent_recv(state_t *state)
{
	char *chanstr;
	if (!(chanstr = arg_pop(state))) {
		fprintf(stderr, "%s\n", AGENT_ERR_NOCHANNEL);
		return (errno = EINVAL), -1;
	}

	lc_ctx_t *lctx;
	lc_socket_t *sock;
	lc_channel_t *chan;
	lc_keyring_t keyring = {0};
	lc_filter_t filter;
	ssize_t byt = 0;
	int rc;

	lctx = lc_ctx_new();
	if (!lctx) return -1;
	sock = lc_socket_new(lctx);
	if (!sock) goto err_ctx_free;
	if (state->ifx) lc_socket_bind(sock, state->ifx);
	chan = lc_channel_new(lctx, chanstr);
	if (!chan) goto err_ctx_free;
	if (lc_channel_bind(sock, chan)) goto err_ctx_free;

	int encryption = 0;
	if (state->defaults.seed) {
		encryption = LC_CODE_SYMM;
		/* set encryption key, if we have one */
		if (agent_set_channel_key(state, chan)) return -1;
	}
	lc_channel_coding_set(chan, LC_CODE_FEC_RQ | LC_CODE_FEC_OTI | encryption);

	/* set keyring + filter, join channel */
	rc = keyring_load(state, &keyring);
	if (rc == -1) goto err_ctx_free;
	E(STATE_VERBOSE, "filter with %zu keys loaded\n", keyring.nkeys);
	filter.keyring = &keyring;
	filter.capbits = 0;
	lc_channel_filter_set(chan, &filter);
	if (lc_channel_join(chan)) goto err_ctx_free;

	agent_datahead_t datahead = {0};
	char buf[32768 - sizeof datahead];
	struct iovec iov[2] = {0};
	struct msghdr msg = {0};
	msg.msg_iov = iov;
	msg.msg_iovlen = sizeof iov / sizeof iov[0];
	iov[0].iov_base = &datahead;
	iov[0].iov_len = sizeof datahead;
	iov[1].iov_base = buf;
	iov[1].iov_len = sizeof buf;
recv_again:
	memset(&datahead, 0, sizeof datahead);
	while (!(datahead.flags & AGENT_EOF)) {
		byt = lc_channel_recvmsg(chan, &msg, 0);
		if (byt == -1) {
			perror("lc_channel_recvmsg");
			rc = -1;
			break;
		}
		/* write data to stdout */
		byt -= sizeof datahead;
		fwrite(buf, 1, byt, stdout);
		E(STATE_VERBOSE, "%zi bytes received\n", byt);
	}
	if (STATE(state, STATE_FOLLOW)) goto recv_again;

	keyring_freekeys(&keyring);
	lc_keyring_free(&keyring);
err_ctx_free:
	lc_ctx_free(lctx);
	return (byt == -1) ? -1 : 0;
}

static int agent_send(state_t *state)
{
	lc_ctx_t *lctx;
	lc_socket_t *sock;
	lc_channel_t *chan;
	lc_token_t token;
	lc_key_t ssk;
	char *chanstr;
	char *data;
	FILE *stream = NULL;
	size_t len = 0;
	ssize_t byt = -1;

	if (!(chanstr = arg_pop(state))) {
		fprintf(stderr, "%s\n", AGENT_ERR_NOCHANNEL);
		return (errno = EINVAL), -1;
	}
	data = arg_pop(state);
	if (STATE(state, STATE_STDIN)) {
		stream = stdin;
	}
	else if (data) {
		len = strlen(data);
		if (len) {
			stream = fmemopen(data, len, "r");
			if (!stream) return -1;
		}
	}
	lctx = lc_ctx_new();
	if (!lctx) goto close_stream;
	sock = lc_socket_new(lctx);
	if (!sock) goto err_ctx_free;
	if (state->ifx) lc_socket_bind(sock, state->ifx);
	if (STATE(state, STATE_LOOPBACK) && lc_socket_loop(sock, 1)) goto err_ctx_free;
	chan = lc_channel_new(lctx, chanstr);
	if (!chan) goto err_ctx_free;
	if (lc_channel_bind(sock, chan)) goto err_ctx_free;
	if (agent_load_channel_token(&token, state, chanstr) == -1) {
		/* no token for channel, self_sign */
		DEBUG("no channel token, self-signing\n");
		lc_keypair_t *signing_key = &state->defaults.keyring.s;
		char *bearer_key = state->defaults.keyring.phex;
		uint64_t valid_sec = KEY_CAP_DEFAULT_SECONDS;
		uint8_t capbits = 0;
		unsigned char bearer_psk[crypto_sign_PUBLICKEYBYTES];
		key_combo_hex2psk(bearer_psk, sizeof bearer_psk, bearer_key);
		if (lc_token_new(&token, signing_key, bearer_psk, chan, capbits, valid_sec))
			goto err_ctx_free;
	}
	else DEBUG("using channel token\n");
	int encryption = 0;
	if (state->defaults.seed) {
		encryption = LC_CODE_SYMM;
		/* set encryption key, if we have one */
		if (agent_set_channel_key(state, chan)) goto err_ctx_free;
	}
	lc_channel_coding_set(chan, LC_CODE_FEC_RQ | LC_CODE_FEC_OTI | encryption);
	lc_channel_rq_overhead(chan, state->overhead);
	lc_channel_ratelimit(chan, state->bpslimit, 0);

	/* set sending key + token */
	ssk.key = state->defaults.keyring.s.sk;
	ssk.keylen = crypto_sign_SECRETKEYBYTES;
	lc_channel_setkey(chan, &ssk, LC_CODE_SIGN);
	lc_channel_token_set(chan, &token);

	/* send */
	agent_datahead_t datahead = {0};
	char buf[32768 - sizeof datahead];
	struct iovec iov[2] = {0};
	struct msghdr msg = {0};
	struct timespec ts;
	size_t byt_out = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = sizeof iov / sizeof iov[0];
	iov[0].iov_base = &datahead;
	iov[0].iov_len = sizeof datahead;
	iov[1].iov_base = buf;
	datahead.flags = 0;
	do {
		if (stream) {
			byt = fread(buf, 1, sizeof buf, stream);
			if (byt == -1) break;
		}
		else byt = 0;
		if (!stream || feof(stream)) datahead.flags |= AGENT_EOF;
		iov[1].iov_len = byt;
		if (clock_gettime(CLOCK_TAI, &ts) == 0) {
			datahead.ts = htobe64(ts.tv_sec * 1e9 + ts.tv_nsec);
		}
		byt = lc_channel_sendmsg(chan, &msg, 0);
		if (byt == -1) DEBUG("lc_channel_sendmsg: %s\n", strerror(errno));
		if (byt > 0) byt_out += byt - sizeof datahead;
	}
	while (stream && !feof(stream));

	if (STATE(state, STATE_VERBOSE)) {
		if (byt == -1) perror("lc_channel_send");
		else fprintf(stderr, "%zu bytes sent\n", byt_out);
	}
err_ctx_free:
	lc_ctx_free(lctx);
close_stream:
	if (stream) fclose(stream);
	return (byt < 0) ? -1 : 0;
}

static int agent_pidfile_unlock(state_t *state)
{
	if (flock(state->lockfd, LOCK_UN) == -1) return -1;
	close(state->lockfd);
	state->lockfd = 0;
	return 0;
}

static int agent_pidfile_write(state_t *state)
{
	char pidbuf[21]; /* fits UINT64_MAX + NUL */
	ssize_t len;
	if (ftruncate(state->lockfd, 0) == -1) goto unlock_pidfile;
	if (snprintf(pidbuf, sizeof pidbuf, "%ld", (long)state->pid) < 0) goto unlock_pidfile;
	len = strlen(pidbuf);
	if (write(state->lockfd, pidbuf, len) != len) goto unlock_pidfile;
	return 0;
unlock_pidfile:
	agent_pidfile_unlock(state);
	return -1;
}

static int agent_pidfile_read(state_t *state)
{
	char pidbuf[21] = {0}; /* fits UINT64_MAX + NUL */
	char *pidfile;
	int rc = -1;
	pidfile = state_pidfile(state);
	if (!pidfile) return -1;
	state->lockfd = open(pidfile, O_RDONLY);
	if (state->lockfd == -1) goto free_pidfile;
	lseek(state->lockfd, 0, SEEK_SET);
	if (read(state->lockfd, pidbuf, sizeof pidbuf) > 0) {
		state->pid = atoi(pidbuf);
		rc = 0;
	}
free_pidfile:
	free(pidfile);
	return rc;
}

/* open and lock pidfile. return -1 on error, fd of pidfile on success */
static int agent_pidfile_lock(state_t *state, int block)
{
	char *pidfile;
	int fd, flags;
	pidfile = state_pidfile(state);
	if (!pidfile) return -1;
create_pidfile:
	fd = open(pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
	if (fd == -1) goto free_pidfile;
	flags = fcntl(fd, F_GETFD);
	if (flags == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) goto close_pidfile;
	if (flock(fd, LOCK_EX | block) == -1) {
		if (agent_pidfile_read(state) == 0) {
			int rc = kill(state->pid, 0);
			if (rc == -1 && errno == ESRCH) {
				/* stale lockfile */
				close(fd);
				if (unlink(pidfile) == 0) goto create_pidfile;
				goto free_pidfile;
			}
		}
		goto close_pidfile;
	}
	state->lockfd = fd;
free_pidfile:
	free(pidfile);
	return fd;
close_pidfile:
	close(fd);
	fd = -1;
	goto free_pidfile;
}

static int agent_reload(state_t *state)
{
	if (agent_pidfile_read(state)) return -1;
	E(STATE_VERBOSE, "sending SIGHUP to pid %i\n", state->pid);
	return kill(state->pid, SIGHUP);
}

static int agent_stop(state_t *state)
{
	if (agent_pidfile_read(state)) return -1;
	E(STATE_VERBOSE, "sending SIGINT to pid %i\n", state->pid);
	return kill(state->pid, SIGINT);
}

/* lc_channel_new() with a wrapper to support older librecast (< 0.9) hashes */
static lc_channel_t *agent_channel_new(state_t *state, char *channel_name, unsigned char *hash)
{
	lc_ctx_t *lctx = lc_socket_ctx(state->sock);
	lc_channel_t *chan;
#ifndef HAVE_LC_CHANNEL_GET_HASH
	/* support librecast < 0.9 */
	struct in6_addr addr = {0};
	hash_generic(hash, HASHSIZE, (unsigned char *)channel_name, strlen(channel_name));
	addr.s6_addr[0] = 0xff;
	addr.s6_addr[1] = 0x1e;
	memcpy(&addr.s6_addr[2], hash, 14);
	chan = lc_channel_init_grp(lctx, &addr, LC_DEFAULT_PORT);
#else
	(void)hash;
	chan = lc_channel_new(lctx, channel_name);
#endif
	return chan;
}

int agent_load_config(state_t *state, int argc, char *argv[], char *home)
{
	int rc;
	if (!state->overhead) state->overhead = RQ_OVERHEAD * 2;
	if (state_defaults_set(state)) return -1;
	if (state_parse_args(state, argc, argv)) return -1;
	if ((rc = state_dirs(state, home))) goto err_free_state;
	if (state->verb > VERB_VERSION) {
		/* every verb except HELP and VERSION require a key */
		rc = key_load_default(state, &state->defaults.keyring);
		if (rc) goto err_free_state;
	}
	if ((rc = state_parse_configfile(state, state->rcfile))) {
		if (errno != ENOENT) goto err_free_state;
	}
	else E(STATE_VERBOSE, "loading rcfile %s\n", state->rcfile);
	return 0;
err_free_state:
	free_state(state);
	return -1;
}

static int agent_join_channel(state_t *state, lc_filter_t *filter, state_chan_t *a)
{
	unsigned char hash[HASHSIZE];
	int onstart = 0;
	if ((a->flags & CHAN_ONSTART)) onstart++;
	if (!(a->flags & CHAN_JOIN)) return onstart;
	a->chan = agent_channel_new(state, a->chan_name, hash);
	if (!a->chan) return -1;
	if (lc_channel_bind(state->sock, a->chan)) return -1;
	lc_channel_coding_set(a->chan, LC_CODE_FEC_RQ | LC_CODE_FEC_OTI);
	lc_channel_filter_set(a->chan, filter);
	if (lc_channel_join(a->chan) == -1) return -1;
	DEBUG("join channel '%s'\n", a->chan_name);
	return onstart;
}

static lc_socket_t *agent_socket_new(void)
{
	lc_ctx_t *lctx = lc_ctx_new();
	if (!lctx) return NULL;
	return lc_socket_new(lctx);
}

static int agent_reload_rcfile(state_t *state)
{
	state_t newstate = {0};
	unsigned char hash[HASHSIZE];
	int rc;

	g_reload = 0;
	rc = agent_load_config(&newstate, state->argc, state->argv, state->dir_home);
	if (rc == -1) return -1;
	newstate.sock = (state->sock) ? state->sock : agent_socket_new();
	if (!newstate.sock) return -1;
	if ((newstate.logfile && !state->logfile)||(!newstate.logfile && state->logfile)
	||((newstate.logfile && state->logfile) && !strcmp(newstate.logfile, state->logfile)))
	{
		/* logfile has changed */
		log_open(&newstate, NULL);
		log_close(state);
	}
	else {
		newstate.log = state->log;
		state->log = NULL;
	}
	if (newstate.ifx != state->ifx && state->chan_head && newstate.chan_head) {
		/* network ifx has changed, rebind socket */
		lc_socket_t *sock = lc_channel_socket(state->chan_head->chan);
		rc = lc_socket_bind(sock, newstate.ifx);
	}
	newstate.lockfd = state->lockfd;
	/* close channels we don't need */
	for (state_chan_t *a = state->chan_head; a; ) {
		int found = 0;
		for (state_chan_t *b = newstate.chan_head; b; b = b->next) {
			if (!strcmp(a->chan_name, b->chan_name)) {
				b->chan = a->chan;
				a->chan = NULL;
				found++;
				break;
			}
		}
		if (!found) {
			/* close channel */
			lc_channel_part(a->chan);
		}
		state_chan_t *tmp = a;
		a = a->next;
		if (tmp == state->chan_head) state->chan_head = a;
		state_channel_free(tmp);
	}
	/* open new channels */
	for (state_chan_t *a = newstate.chan_head; a; a = a->next) {
		if (!a->chan && (a->flags & CHAN_JOIN)) {
			a->chan = agent_channel_new(&newstate, a->chan_name, hash);
			if (!a->chan) return -1;
		}
	}
	state->chan_head = NULL;
	free_state(state);
	*state = newstate;
	return rc;
}

static int agent_channel_exec(state_t *state, state_chan_t *schan, struct msghdr *msg, ssize_t len)
{
	enum { READ, WRITE };
	int rc = 0;
	char cwd[PATH_MAX];
	if (!(schan->flags & CHAN_ENABLE)) {
		DEBUG("channel '%s' is not enabled.\n", schan->chan_name);
		return 0;
	}
	if (getcwd(cwd, sizeof cwd) == NULL) return -1;
	if (state->dir_home || schan->dir) {
		/* first change to home dir */
		if (state->dir_home && chdir(state->dir_home)) return -1;
		/* command directory is relative to home dir (or absolute) */
		if (schan->dir && chdir(schan->dir)) goto restore_cwd;
	}
	for (state_cmd_t *cmd = schan->cmd; cmd; cmd = cmd->next) {
		pid_t pid;
		int pipefd[2];
		if (msg) {
			rc = pipe(pipefd);
			if (rc == -1) break;
		}
		DEBUG("channel '%s' executing cmd '%s'\n", schan->chan_name, cmd->cmd);
		pid = fork();
		if (pid == -1) break;
		if (!pid) {
			if (msg) {
				close(pipefd[WRITE]);
				dup2(pipefd[READ], STDIN_FILENO);
			}
			execl("/bin/sh", "sh", "-c", cmd->cmd, (char *) NULL);
			/* only reached on error */
			_exit(EXIT_FAILURE);
		}
		if (msg) {
			agent_datahead_t *datahead = msg->msg_iov[0].iov_base;
			ssize_t byt;
write_again:
			close(pipefd[READ]); /* close read end */
			byt = write(pipefd[WRITE], msg->msg_iov[1].iov_base, len);
			DEBUG("channel '%s' piped %zi bytes to cmd\n", schan->chan_name, byt);
			if (byt == len && !(datahead->flags & AGENT_EOF)) {
				byt = lc_channel_recvmsg(schan->chan, msg, 0);
				if (byt != -1) {
					len = byt - (ssize_t)sizeof *datahead;
					goto write_again;
				}
			}
			close(pipefd[WRITE]);
			DEBUG("channel '%s' closing pipe\n", schan->chan_name);
		}
		int wstatus;
		waitpid(pid, &wstatus, 0);
		/* command must succeed, unless CMD_TRY is set */
		rc = (WIFEXITED(wstatus)) ? WEXITSTATUS(wstatus) : -1;
		DEBUG("channel '%s' cmd exited %i\n", schan->chan_name, rc);
		if (cmd->flags & CMD_TRY) continue;
		if (rc) break;
	}
	if (schan->flags & CHAN_RELOAD) {
		INFO("reload %s\n", PACKAGE_NAME);
		agent_reload_rcfile(state);
	}
	else if (schan->flags & CHAN_RESTART) {
		INFO("restarting %s\n", PACKAGE_NAME);
		execvp(PACKAGE_NAME, state->argv);
		perror(PACKAGE_NAME);
		ERROR("failed to restart agent\n");
	}

restore_cwd:
	if (chdir(cwd)) return -1;
	return rc;
}

static int agent_server(state_t *state)
{
	char buf[32768];
	lc_ctx_t *lctx;
	lc_channel_t *dst;
	state_chan_t *state_chan;
	lc_keyring_t keyring;
	lc_filter_t filter;
	ssize_t byt;
	int onstart = 0;
	int rc;

	struct sigaction act = {0};
	act.sa_sigaction = &handle_signal;
	if (sigaction(SIGINT, &act, NULL) == -1) {
		perror("sigaction");
		_exit(EXIT_FAILURE);
	}
	if (sigaction(SIGHUP, &act, NULL) == -1) {
		perror("sigaction");
		_exit(EXIT_FAILURE);
	}

	E(STATE_VERBOSE, "%s started (pid: %i)\n", PACKAGE_NAME, getpid());
	INFO("%s started (pid: %i)\n", PACKAGE_NAME, getpid());

	/* prepare context & socket */
	lctx = lc_ctx_new();
	if (!lctx) return -1;
	state->sock = lc_socket_new(lctx);
	if (!state->sock) goto err_ctx_free;
	if (state->ifx) lc_socket_bind(state->sock, state->ifx);

	/* wait for at least one IPv6 multicast interface to be ready */
	for (int ifaces = 0; !ifaces; ) {
		struct ifaddrs *ifap;
		rc = getifaddrs(&ifap);
		if (rc == -1) return -1;
		for (struct ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next) {
			if (ifa->ifa_addr == NULL) continue;
			if (ifa->ifa_addr->sa_family != AF_INET6) continue; /* IPv6 */
			if (!(ifa->ifa_flags & IFF_MULTICAST)) continue;    /* multicast */
			/* test bind() on interface to ensure DAD is finished */
			int sock = socket(AF_INET6, SOCK_DGRAM, 0);
			if (sock == -1) continue;
			rc = bind(sock, ifa->ifa_addr, sizeof(struct sockaddr_in6));
			close(sock);
			if (rc == -1) continue;
			ifaces++;
		}
		freeifaddrs(ifap);
		if (ifaces) {
			DEBUG("%i interfaces detected on startup\n", ifaces);
			break;
		}
		usleep(100);
	}

	/* prepare keyring + filter */
	rc = keyring_load(state, &keyring);
	if (rc == -1) goto err_ctx_free;
	E(STATE_VERBOSE, "filter with %zu keys loaded\n", keyring.nkeys);
	filter.keyring = &keyring;
	filter.capbits = 0;

	/* iterate through channels, join, set filter */
	for (state_chan_t *a = state->chan_head; a; a = a->next) {
		rc = agent_join_channel(state, &filter, a);
		if (rc == -1) goto err_free_keys;
		onstart += rc;
	}
	/* all channels now listening, run any onstart events */
	if (onstart) {
		DEBUG("processing onstart events\n");
		for (state_chan_t *a = state->chan_head; a; a = a->next) {
			if (a->flags & CHAN_ONSTART) agent_channel_exec(state, a, NULL, 0);
		}
	}

	/* server loop */
	agent_datahead_t datahead;
	struct iovec iov[2];
	struct msghdr msg = {0};
	iov[0].iov_base = &datahead;
	iov[0].iov_len = sizeof datahead;
	iov[1].iov_base = buf;
	iov[1].iov_len = sizeof buf;
	msg.msg_iov = iov;
	msg.msg_iovlen = sizeof iov / sizeof iov[0];
	for (;;) {
		byt = lc_socket_multi_recvmsg(state->sock, &msg, 0, &dst);
		if (byt == -1) {
			if (errno != EINTR) break;
			if (g_reload) agent_reload_rcfile(state);
			if (g_stop) break;
			continue;
		}
		struct in6_addr *addr = lc_channel_in6addr(dst);
		ssize_t len = byt - sizeof datahead;
		state_chan = state_chan_by_addr(state, addr);
		DEBUG("channel '%s' received %zi bytes\n", state_chan->chan_name, len);
		if (state_chan) rc = agent_channel_exec(state, state_chan, &msg, len);
	}
	INFO("%s stopping (pid: %i)\n", PACKAGE_NAME, getpid());
err_free_keys:
	keyring_freekeys(&keyring);
	lc_keyring_free(&keyring);
err_ctx_free:
	lc_ctx_free(lctx);
	return agent_pidfile_unlock(state);
}

static int agent_start(state_t *state)
{
	if (agent_pidfile_lock(state, LOCK_NB) == -1) {
		agent_pidfile_read(state);
		fprintf(stderr, AGENT_ERR_ISRUNNING, PACKAGE_NAME, state->pid);
		fputc('\n', stderr);
		return -1;
	}
	pid_t pid = fork();
	if (pid == -1) {
		agent_pidfile_unlock(state);
		return -1;
	}
	if (pid) {
		struct sigaction act = {0};
		act.sa_sigaction = &handle_signal;
		if (sigaction(SIGCONT, &act, NULL) == -1) return -1;
		state->pid = pid;
		pause(); /* wait until child has written pid */
	}
	else {
		state->pid = getpid();
		int rc = agent_pidfile_write(state);
		kill(getppid(), SIGCONT); /* tell parent we've written pid */
		if (rc == -1) return -1;
		rc = agent_server(state);
		free_state(state);
		exit((rc == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
	}
	return 0;
}

static int agent_exec(state_t *state)
{
	char *chanstr = arg_pop(state);
	if (chanstr) {
		/* find channel */
		state_chan_t *state_chan = state_chan_by_name(state, chanstr);
		if (!state_chan) {
			fprintf(stderr, AGENT_ERR_NOSUCHCHANNEL, chanstr);
			fputc('\n', stderr);
			return (errno = EINVAL), -1;
		}
		/* execute channel commands */
		return agent_channel_exec(state, state_chan, NULL, 0);
	}
	return 0;
}

int agent_run(state_t *state)
{
	int rc = -1;
#define X(a,b,c,d) case c: return d(state);
	switch (state->verb) {
		STATE_VERBS
	}
#undef X
	return rc;
}

int agent(state_t *state, int argc, char *argv[])
{
	int rc;
	if (agent_load_config(state, argc, argv, NULL)) return EXIT_FAILURE;
	rc = agent_run(state);
	free_state(state);
	return (rc == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
