#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 #include #include #include #include #include #include /* required with -std=c99 */ #include /* 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); }