From 3e3f47ec2fdbfaf0682d9790a99264a4e60e9bf1 Mon Sep 17 00:00:00 2001
From: Jonathan McDowell <noodles@earth.li>
Date: Tue, 5 Apr 2011 21:22:39 -0700
Subject: [PATCH] Add keydctl for talking to keyd backend

  The regular keydb functions for talking to keyd work fine for key
  related operations, but there are extra things we want to do with
  keyd (such as checking its status or asking it to cleanly exit) that
  there's no way to do at present. Add keydctl to provide a way to
  access these additional features.
---
 Makefile.in |  20 ++++--
 keydctl.8   |  50 +++++++++++++
 keydctl.c   | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 267 insertions(+), 5 deletions(-)
 create mode 100644 keydctl.8
 create mode 100644 keydctl.c

diff --git a/Makefile.in b/Makefile.in
index 546f3a8..66a3b5c 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -28,9 +28,9 @@ SRCS = armor.c parsekey.c merge.c keyid.c md5.c sha1.c main.c getcgi.c mem.c \
 PROGS_LDFLAGS_EXTRA =
 
 ifeq (x@KEYD@, xyes)
-PROGS += keyd
+PROGS += keyd keydctl
 KEYDB_OBJ = keydb_keyd.o
-SRCS += keyd.c keydb_keyd.c
+SRCS += keyd.c keydb_keyd.c keydctl.c
 else
 KEYDB_OBJ = keydb_$(DBTYPE).o
 endif
@@ -38,9 +38,9 @@ endif
 ifeq (x@DBTYPE@, xdynamic)
 LIBS += -ldl
 BACKENDS = $(foreach be,@BACKENDS@,libkeydb_$(be).so)
-PROGS += keyd
+PROGS += keyd keydctl
 PROGS_LDFLAGS_EXTRA = -rdynamic
-SRCS += keyd.c
+SRCS += keyd.c keydctl.c
 endif
 
 OBJS = stats.o cleankey.o $(CORE_OBJS) $(KEYDB_OBJ)
@@ -58,17 +58,22 @@ install: $(PROGS) onak.conf $(BACKENDS)
 	install onak-mail.pl $(DESTDIR)/@libdir@/onak
 	install onak splitkeys $(DESTDIR)/@bindir@
 	install onak.1 splitkeys.1 $(DESTDIR)/@mandir@/man1
-	install keyd.8 onak-mail.pl.8 $(DESTDIR)/@mandir@/man8
+	install keyd.8 keydctl.8 onak-mail.pl.8 $(DESTDIR)/@mandir@/man8
 ifeq (x@DBTYPE@, xdynamic)
 	install $(BACKENDS) $(DESTDIR)/@libdir@/onak/backends
 	install -d $(DESTDIR)/@sbindir@
 	install keyd $(DESTDIR)/@sbindir@
+	install keydctl $(DESTDIR)/@bindir@
 endif
 
 keyd: keyd.o $(CORE_OBJS) keydb_$(DBTYPE).o
 	$(CC) $(LDFLAGS) $(PROGS_LDFLAGS_EXTRA) \
 		-o keyd keyd.o $(CORE_OBJS) keydb_$(DBTYPE).o $(LIBS)
 
+keydctl: keydctl.o onak-conf.o ll.o log.o
+	$(CC) $(LDFLAGS) $(PROGS_LDFLAGS_EXTRA) \
+		-o keydctl keydctl.o onak-conf.o ll.o log.o $(LIBS)
+
 libkeydb_db4.so: keydb_db4.o
 	$(CC) -shared $(DB4LIBS) -o libkeydb_db4.so keydb_db4.o $(CORE_OBJS)
 
@@ -118,6 +123,11 @@ onak-conf.o: onak-conf.c onak-conf.h
 	$(CC) $(CFLAGS) -DCONFIGFILE=\"@sysconfdir@/onak.conf\" \
 		-DDBFUNCS=keydb_@DBTYPE@_funcs -c onak-conf.c
 
+# HACK: onak-conf.o needs to be able to see keydb_@DBTYPE@_funcs, but
+# keydctl doesn't want to link against the DB stuff. To be fixed more cleanly.
+keydctl.o: keydctl.c keyd.h
+	$(CC) $(CFLAGS) -DDBFUNCS=keydb_@DBTYPE@_funcs -c keydctl.c
+
 onak-mail.pl: onak-mail.pl.in
 	sed 's:@CONFIG@:@sysconfdir@/onak.conf:g' < onak-mail.pl.in > onak-mail.pl
 	chmod +x onak-mail.pl
diff --git a/keydctl.8 b/keydctl.8
new file mode 100644
index 0000000..44e7ba6
--- /dev/null
+++ b/keydctl.8
@@ -0,0 +1,50 @@
+.TH KEYD 8
+.SH NAME
+keydctl \- control an onak keyd instance
+.SH SYNOPSIS
+.PP
+.B keydctl
+[
+.B options
+]
+.B command
+.SH DESCRIPTION
+.PP
+keydctl is a command line client for interacting with a backend keyd
+daemon. It's intended to perform functions that are specifically related
+to keyd rather than being generic keyserver functions. See
+.BR onak(1)
+for details about how to perform key related operations.
+.SS "Options"
+.TP
+\fB\-c \fIFILE\fR\fR
+Use \fIFILE\fR as the config file instead of the default.
+.TP
+\fB\-h\fR
+Display help text.
+.SS "Commands"
+.TP
+.B check
+Query if keyd is running and accepting commands. Returns 0 if it is, 1
+otherwise. Outputs nothing to stdout/stderr.
+.TP
+.B quit
+Request that keyd exits cleanly
+.TP
+.B status
+Display the status of a running keyd. Currently just the protocol version
+in use.
+.SH FILES
+.br
+.nf
+.\" set tabstop to longest possible filename, plus a wee bit
+.ta \w'/usr/lib/perl/getopts.pl   'u
+\fI/etc/onak.conf\fR	default configuration file
+.SH NOTES
+This man page could probably do with some more details.
+.SH "SEE ALSO"
+.BR onak (1)
+.BR keyd (8)
+.SH AUTHOR
+onak was written by Jonathan McDowell <noodles@earth.li>. It can be found at
+http://www.earth.li/projectpurple/progs/onak.html
diff --git a/keydctl.c b/keydctl.c
new file mode 100644
index 0000000..b786fea
--- /dev/null
+++ b/keydctl.c
@@ -0,0 +1,202 @@
+/*
+ * keydctl.c - A simple program to control a running keyd instance
+ *
+ * Copyright 2011 Jonathan McDowell <noodles@earth.li>
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "config.h"
+#include "keyd.h"
+#include "onak-conf.h"
+
+/* HACK: We need to stop onak-conf.o requiring this. */
+void *DBFUNCS = NULL;
+
+static int keyd_fd = -1;
+static int verbose = 0;
+
+static int keyd_do_command(enum keyd_ops cmd, void *buf, size_t len)
+{
+	uint32_t tmp;
+
+	if (keyd_fd < 0) {
+		return -1;
+	}
+
+	tmp = cmd;
+	if (write(keyd_fd, &tmp, sizeof(tmp)) != sizeof(tmp)) {
+		if (verbose >= 0) {
+			fprintf(stderr,
+				"Couldn't write keyd command %d: %s (%d)\n",
+				cmd, strerror(errno), errno);
+		}
+		exit(EXIT_FAILURE);
+	} else if (read(keyd_fd, &tmp, sizeof(tmp)) != sizeof(tmp)) {
+		if (verbose >= 0) {
+			fprintf(stderr,
+				"Couldn't read keyd command %d reply: "
+				"%s (%d)\n",
+				cmd, strerror(errno), errno);
+			}
+		exit(EXIT_FAILURE);
+	} else if (tmp != KEYD_REPLY_OK) {
+		return -1;
+	} else if (buf == NULL) {
+		return 0;
+	} else if (read(keyd_fd, &tmp, sizeof(tmp)) != sizeof(tmp)) {
+		if (verbose >= 0) {
+			fprintf(stderr,
+				"Couldn't read keyd command %d reply length: "
+				"%s (%d)\n",
+				cmd, strerror(errno), errno);
+		}
+		exit(EXIT_FAILURE);
+	} else if (tmp > len) {
+		/* TODO: Read what we can into buf and skip the rest */
+		return -1;
+	} else {
+		return read(keyd_fd, buf, tmp);
+	}
+}
+
+static void keyd_connect(void)
+{
+	struct sockaddr_un sock;
+	uint32_t	   reply = KEYD_REPLY_UNKNOWN_CMD;
+
+	keyd_fd = socket(PF_UNIX, SOCK_STREAM, 0);
+	if (keyd_fd < 0) {
+		if (verbose >= 0) {
+			fprintf(stderr,
+				"Couldn't open socket: %s (%d)\n",
+				strerror(errno),
+				errno);
+		}
+		exit(EXIT_FAILURE);
+	}
+
+	sock.sun_family = AF_UNIX;
+	snprintf(sock.sun_path, sizeof(sock.sun_path) - 1, "%s/%s",
+			config.db_dir,
+			KEYD_SOCKET);
+	if (connect(keyd_fd, (struct sockaddr *) &sock, sizeof(sock)) < 0) {
+		if (verbose >= 0) {
+			fprintf(stderr,
+				"Couldn't connect to socket %s: %s (%d)\n",
+				sock.sun_path,
+				strerror(errno),
+				errno);
+		}
+		exit(EXIT_FAILURE);
+	}
+
+	keyd_do_command(KEYD_CMD_VERSION, &reply, sizeof(reply));
+	if (reply != keyd_version) {
+		if (verbose >= 0) {
+			fprintf(stderr, "Error! keyd protocol version "
+				"mismatch. (us = %d, it = %d)\n",
+				keyd_version, reply);
+		}
+		exit(EXIT_FAILURE);
+	}
+
+	return;
+}
+
+static void keyd_close(void)
+{
+	uint32_t cmd = KEYD_CMD_CLOSE;
+
+	if (write(keyd_fd, &cmd, sizeof(cmd)) != sizeof(cmd) && verbose >= 0) {
+		fprintf(stderr, "Couldn't send close cmd: %s (%d)\n",
+				strerror(errno),
+				errno);
+	}
+
+	if (shutdown(keyd_fd, SHUT_RDWR) < 0 && verbose >= 0) {
+		fprintf(stderr, "Error shutting down socket: %d\n",
+				errno);
+	}
+	if (close(keyd_fd) < 0 && verbose >= 0) {
+		fprintf(stderr, "Error closing down socket: %d\n",
+				errno);
+	}
+	keyd_fd = -1;
+
+	return;
+
+}
+
+static void keyd_status(void)
+{
+	uint32_t reply;
+
+	keyd_do_command(KEYD_CMD_VERSION, &reply, sizeof(reply));
+	printf("Using keyd protocol version %d.\n", reply);
+
+	return;
+}
+
+static void usage(void)
+{
+	puts("keydctl " PACKAGE_VERSION " - control an onak keyd instance.\n");
+	puts("Usage:\n");
+	puts("\tonak [options] <command> <parameters>\n");
+	puts("\tCommands:\n");
+	puts("\tcheck    - check if keyd is running");
+	puts("\tquit     - request that keyd cleanly shuts down");
+	puts("\tstatus   - display running keyd status");
+	exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+	int	 optchar;
+	char	*configfile = NULL;
+
+	while ((optchar = getopt(argc, argv, "c:h")) != -1 ) {
+		switch (optchar) {
+		case 'c':
+			configfile = strdup(optarg);
+			break;
+		case 'h':
+		default:
+			usage();
+			break;
+		}
+	}
+
+	readconfig(configfile);
+	free(configfile);
+	configfile = NULL;
+
+	if ((argc - optind) < 1) {
+		usage();
+	} else if (!strcmp("check", argv[optind])) {
+		/* Just do the connect and close quietly */
+		verbose = -1;
+		keyd_connect();
+		keyd_close();
+	} else if (!strcmp("status", argv[optind])) {
+		keyd_connect();
+		keyd_status();
+		keyd_close();
+	} else if (!strcmp("quit", argv[optind])) {
+		keyd_connect();
+		keyd_do_command(KEYD_CMD_QUIT, NULL, 0);
+		keyd_close();
+	} else {
+		usage();
+	}
+
+
+	exit(EXIT_SUCCESS);
+}
-- 
2.39.5