Add keydctl for talking to keyd backend
authorJonathan McDowell <noodles@earth.li>
Wed, 6 Apr 2011 04:22:39 +0000 (21:22 -0700)
committerJonathan McDowell <noodles@earth.li>
Wed, 6 Apr 2011 04:22:39 +0000 (21:22 -0700)
  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
keydctl.8 [new file with mode: 0644]
keydctl.c [new file with mode: 0644]

index 546f3a81da9ed26d3eb8bb6b4d62feff65a27a8e..66a3b5c60a481e764cc24cbc17a38e71441c85b1 100644 (file)
@@ -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 (file)
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 (file)
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);
+}