aboutsummaryrefslogtreecommitdiff
path: root/daemon.c
diff options
context:
space:
mode:
authorPaul Selkirk <paul@psgd.org>2016-06-02 14:01:39 -0400
committerPaul Selkirk <paul@psgd.org>2016-06-02 14:01:39 -0400
commitf94203f344a7eabba834702dd171ae6c5c01c729 (patch)
tree5706fa9a3fa55ba408abee5b3c874cc170f0de11 /daemon.c
parent63fd94a724893152592b5f318e7d98f2be0ede74 (diff)
Add RPC client daemon.
Diffstat (limited to 'daemon.c')
-rw-r--r--daemon.c343
1 files changed, 343 insertions, 0 deletions
diff --git a/daemon.c b/daemon.c
new file mode 100644
index 0000000..601fc0f
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,343 @@
+#define DEBUG
+/*
+ * daemon.c
+ * --------
+ * A daemon to arbitrate shared access to a serial connection to the HSM.
+ *
+ * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the NORDUnet nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <poll.h>
+#include <getopt.h> /* required with -std=c99 */
+#include <termios.h> /* for default speed */
+
+#include "slip_internal.h"
+#include "xdr_internal.h"
+
+static char usage[] =
+ "usage: %s [-n socketname] [-d ttydevice] [-s ttyspeed]\n";
+
+/* select() is hopelessly broken, and epoll() is Linux-specific, so we'll use
+ * poll() until such a time as libevent or libev seems more appropriate.
+ * Unfortunately, poll() doesn't come with any macros or functions to manage
+ * the pollfd array, so we have to invent them.
+ */
+
+static struct pollfd *pollfds = NULL;
+static nfds_t nfds = 0;
+static nfds_t npollfds = 0;
+
+static void poll_add(int fd)
+{
+ /* add 4 entries at a time to avoid having to realloc too often */
+#define NNEW 4
+
+ /* expand the array if necessary */
+ if (nfds == npollfds) {
+ npollfds = nfds + NNEW;
+ pollfds = realloc(pollfds, npollfds * sizeof(struct pollfd));
+ if (pollfds == NULL) {
+ perror("realloc");
+ exit(EXIT_FAILURE);
+ }
+ /* zero the new entries for hygiene */
+ memset(&pollfds[nfds], 0, NNEW * sizeof(struct pollfd));
+ }
+
+ /* populate the new entry */
+ pollfds[nfds].fd = fd;
+ pollfds[nfds].events = POLLIN;
+ ++nfds;
+}
+
+static void poll_remove(int fd)
+{
+ nfds_t i;
+
+ /* search the pollfd array */
+ for (i = 0; i < nfds; ++i) {
+ if (pollfds[i].fd == fd) {
+ /* shift remainder of the array left by one */
+ memmove(&pollfds[i], &pollfds[i + 1], (nfds - i - 1) * sizeof(struct pollfd));
+ /* zero the last entry for hygiene */
+ memset(&pollfds[nfds - 1], 0, sizeof(struct pollfd));
+ --nfds;
+ return;
+ }
+ }
+ /* if it's not found, return without an error */
+}
+
+#ifndef MAX_PKT_SIZE /* move this to hal_internal.h */
+#define MAX_PKT_SIZE 4096
+#endif
+
+typedef struct {
+ size_t len;
+ uint8_t buf[MAX_PKT_SIZE];
+} rpc_buffer_t;
+static rpc_buffer_t ibuf, obuf;
+
+#ifndef DEVICE
+#define DEVICE "/dev/ttyUSB0"
+#endif
+#ifndef SPEED
+#define SPEED B921600
+#endif
+
+#ifndef SOCKET_NAME
+#define SOCKET_NAME "/tmp/cryptechd.socket"
+#endif
+char *socket_name = SOCKET_NAME;
+
+/* Set up an atexit handler to remove the filesystem entry for the unix domain
+ * socket. This will trigger on error exits, but not on the "normal" SIGKILL.
+ */
+void atexit_cleanup(void)
+{
+ unlink(socket_name);
+}
+
+#ifdef DEBUG
+static void hexdump(uint8_t *buf, uint32_t len)
+{
+ for (uint32_t i = 0; i < len; ++i)
+ printf("%02x%c", buf[i], ((i & 0x07) == 0x07) ? '\n' : ' ');
+ if ((len & 0x07) != 0)
+ printf("\n");
+}
+#endif
+
+int main(int argc, char *argv[])
+{
+ struct sockaddr_un name;
+ int ret;
+ int lsock;
+ int dsock;
+ int opt;
+ char *device = DEVICE;
+ speed_t speed = SPEED;
+
+ while ((opt = getopt(argc, argv, "hn:d:s:")) != -1) {
+ switch (opt) {
+ case 'h':
+ printf(usage, argv[0]);
+ exit(EXIT_SUCCESS);
+ case 'n':
+ socket_name = optarg;
+ break;
+ case 'd':
+ device = optarg;
+ break;
+ case 's':
+ switch (atoi(optarg)) {
+ case 115200:
+ speed = B115200;
+ break;
+ case 921600:
+ speed = B921600;
+ break;
+ default:
+ printf("invalid speed value %s\n", optarg);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ default:
+ printf(usage, argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (atexit(atexit_cleanup) != 0) {
+ perror("atexit");
+ exit(EXIT_FAILURE);
+ }
+
+ if (hal_serial_init(device, speed) != HAL_OK)
+ exit(EXIT_FAILURE);
+
+ int serial_fd = hal_serial_get_fd();
+ poll_add(serial_fd);
+
+ /* Remove the filesystem entry for the unix domain socket. The usual way
+ * to stop a daemon is SIGKILL, which we can't catch, so the file remains,
+ * and will prevent us from binding the socket.
+ *
+ * XXX We should also scan the process table, to make sure the daemon
+ * isn't already running.
+ */
+ unlink(socket_name);
+
+ /* Create the listening socket.
+ */
+ lsock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (lsock == -1) {
+ perror("socket");
+ exit(EXIT_FAILURE);
+ }
+ poll_add(lsock);
+
+ /* For portability, clear the whole address structure, since some
+ * implementations have additional (nonstandard) fields in the structure.
+ */
+ memset(&name, 0, sizeof(struct sockaddr_un));
+
+ /* Bind the listening socket.
+ */
+ name.sun_family = AF_UNIX;
+ strncpy(name.sun_path, socket_name, sizeof(name.sun_path) - 1);
+ ret = bind(lsock, (const struct sockaddr *) &name, sizeof(struct sockaddr_un));
+ if (ret == -1) {
+ perror("bind");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Prepare to accept connections.
+ */
+ ret = listen(lsock, 20);
+ if (ret == -1) {
+ perror("listen");
+ exit(EXIT_FAILURE);
+ }
+
+ /* The main loop.
+ */
+ for (;;) {
+
+ /* Blocking poll on all descriptors of interest.
+ */
+ ret = poll(pollfds, nfds, -1);
+ if (ret == -1) {
+ perror("poll");
+ exit(EXIT_FAILURE);
+ }
+
+ for (nfds_t i = 0; i < nfds; ++i) {
+ if (pollfds[i].revents != 0) {
+ /* XXX POLLERR|POLLHUP|POLLNVAL */
+
+ /* serial port */
+ if (pollfds[i].fd == serial_fd) {
+ int complete;
+ hal_slip_recv_char(ibuf.buf, &ibuf.len, sizeof(ibuf.buf), &complete);
+ if (complete) {
+#ifdef DEBUG
+ printf("serial port received response:\n");
+ hexdump(ibuf.buf, ibuf.len);
+#endif
+ /* We've got a complete rpc response packet. */
+ const uint8_t *bufptr = ibuf.buf;
+ const uint8_t * const limit = ibuf.buf + ibuf.len;
+ uint32_t sock;
+ /* First word of the response is the client ID. */
+ hal_xdr_decode_int(&bufptr, limit, &sock);
+ /* Pass response on to the client that requested it. */
+ send(sock, bufptr, ibuf.len - 4, 0);
+ /* Reinitialize the receive buffer. */
+ memset(&ibuf, 0, sizeof(ibuf));
+ }
+ }
+
+ /* listening socket */
+ else if (pollfds[i].fd == lsock) {
+ /* Accept incoming connection. */
+ dsock = accept(lsock, NULL, NULL);
+ if (ret == -1) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+ poll_add(dsock);
+#ifdef DEBUG
+ printf("listening socket accept data socket %d\n", dsock);
+#endif
+ }
+
+ /* client data socket */
+ else {
+ uint8_t *bufptr = obuf.buf;
+ const uint8_t * const limit = obuf.buf + MAX_PKT_SIZE;
+ /* First word of the request is the client ID. */
+ hal_xdr_encode_int(&bufptr, limit, pollfds[i].fd);
+ /* Get the client's rpc request packet. */
+ obuf.len = recv(pollfds[i].fd, bufptr, MAX_PKT_SIZE - 4, 0);
+#ifdef DEBUG
+ printf("data socket %d received request:\n", pollfds[i].fd);
+ hexdump(obuf.buf, obuf.len + 4);
+#endif
+
+ /* Fill in the client handle arg as needed. */
+ uint32_t opcode;
+ hal_xdr_decode_int((const uint8_t ** const)&bufptr, limit, &opcode);
+ switch (opcode) {
+ case RPC_FUNC_SET_PIN:
+ case RPC_FUNC_LOGIN:
+ case RPC_FUNC_LOGOUT:
+ case RPC_FUNC_IS_LOGGED_IN:
+ case RPC_FUNC_HASH_INITIALIZE:
+ case RPC_FUNC_PKEY_LOAD:
+ case RPC_FUNC_PKEY_FIND:
+ case RPC_FUNC_PKEY_GENERATE_RSA:
+ case RPC_FUNC_PKEY_GENERATE_EC:
+ /* first argument is client handle */
+ hal_xdr_encode_int(&bufptr, limit, pollfds[i].fd);
+ break;
+ default:
+ break;
+ }
+
+ if (obuf.len > 0) {
+#ifdef DEBUG
+ printf("passing to serial port\n");
+#endif
+ /* Pass it on to the serial port. */
+ hal_slip_send(obuf.buf, obuf.len + 4);
+ }
+ else {
+#ifdef DEBUG
+ printf("closing data socket\n");
+#endif
+ /* Client has closed the socket. */
+ close(pollfds[i].fd);
+ poll_remove(pollfds[i].fd);
+ }
+ /* Reinitialize the transmit buffer. */
+ memset(&obuf, 0, sizeof(obuf));
+ }
+ }
+ }
+ }
+
+ /*NOTREACHED*/
+ exit(EXIT_SUCCESS);
+}