diff options
Diffstat (limited to 'comm/third_party/libgcrypt/src/gcryptrnd.c')
-rw-r--r-- | comm/third_party/libgcrypt/src/gcryptrnd.c | 680 |
1 files changed, 680 insertions, 0 deletions
diff --git a/comm/third_party/libgcrypt/src/gcryptrnd.c b/comm/third_party/libgcrypt/src/gcryptrnd.c new file mode 100644 index 0000000000..b13931b6e8 --- /dev/null +++ b/comm/third_party/libgcrypt/src/gcryptrnd.c @@ -0,0 +1,680 @@ +/* gcryptrnd.c - Libgcrypt Random Number Daemon + * Copyright (C) 2006 Free Software Foundation, Inc. + * + * Gcryptend is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * Gcryptrnd is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* We require vsyslog pth + We need to test for: setrlimit + + We should also prioritize requests. This is best done by putting + the requests into queues and have a main thread processing these + queues. + + */ + +#include <config.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <assert.h> +#include <time.h> +#include <sys/times.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdarg.h> +#include <syslog.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include <pth.h> +#include <gcrypt.h> + +#define PGM "gcryptrnd" +#define MYVERSION_LINE PGM " (Libgcrypt) " VERSION +#define BUGREPORT_LINE "\nReport bugs to <bug-libgcrypt@gnupg.org>.\n" + +/* Pth wrapper function definitions. */ +GCRY_THREAD_OPTION_PTH_IMPL; + + +/* Flag set to true if we have been daemonized. */ +static int running_detached; +/* Flag indicating that a shutdown has been requested. */ +static int shutdown_pending; +/* Counter for active connections. */ +static int active_connections; + + + +/* Local prototypes. */ +static void serve (int listen_fd); + + + + + +/* To avoid that a compiler optimizes certain memset calls away, these + macros may be used instead. */ +#define wipememory2(_ptr,_set,_len) do { \ + volatile char *_vptr=(volatile char *)(_ptr); \ + size_t _vlen=(_len); \ + while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ + } while(0) +#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) + + + + +/* Error printing utility. PRIORITY should be one of syslog's + priority levels. This functions prints to the stderr or syslog + depending on whether we are already daemonized. */ +static void +logit (int priority, const char *format, ...) +{ + va_list arg_ptr; + + va_start (arg_ptr, format) ; + if (running_detached) + { + vsyslog (priority, format, arg_ptr); + } + else + { + fputs (PGM ": ", stderr); + vfprintf (stderr, format, arg_ptr); + putc ('\n', stderr); + } + va_end (arg_ptr); +} + +/* Callback used by libgcrypt for logging. */ +static void +my_gcry_logger (void *dummy, int level, const char *format, va_list arg_ptr) +{ + (void)dummy; + + /* Map the log levels. */ + switch (level) + { + case GCRY_LOG_CONT: level = LOG_INFO /* FIXME */; break; + case GCRY_LOG_INFO: level = LOG_INFO; break; + case GCRY_LOG_WARN: level = LOG_WARNING; break; + case GCRY_LOG_ERROR:level = LOG_ERR; break; + case GCRY_LOG_FATAL:level = LOG_CRIT; break; + case GCRY_LOG_BUG: level = LOG_CRIT; break; + case GCRY_LOG_DEBUG:level = LOG_DEBUG; break; + default: level = LOG_ERR; break; + } + if (running_detached) + { + vsyslog (level, format, arg_ptr); + } + else + { + fputs (PGM ": ", stderr); + vfprintf (stderr, format, arg_ptr); + if (!*format || format[strlen (format)-1] != '\n') + putc ('\n', stderr); + } +} + + +/* The cleanup handler - used to wipe out the secure memory. */ +static void +cleanup (void) +{ + gcry_control (GCRYCTL_TERM_SECMEM ); +} + + +/* Make us a daemon and open the syslog. */ +static void +daemonize (void) +{ + int i; + pid_t pid; + + fflush (NULL); + + pid = fork (); + if (pid == (pid_t)-1) + { + logit (LOG_CRIT, "fork failed: %s", strerror (errno)); + exit (1); + } + if (pid) + exit (0); + + if (setsid() == -1) + { + logit (LOG_CRIT, "setsid() failed: %s", strerror(errno)); + exit (1); + } + + signal (SIGHUP, SIG_IGN); + + pid = fork (); + if (pid == (pid_t)-1) + { + logit (LOG_CRIT, PGM ": second fork failed: %s", strerror (errno)); + exit (1); + } + if (pid) + exit (0); /* First child exits. */ + + running_detached = 1; + + if (chdir("/")) + { + logit (LOG_CRIT, "chdir(\"/\") failed: %s", strerror (errno)); + exit (1); + } + umask (0); + + for (i=0; i <= 2; i++) + close (i); + + openlog (PGM, LOG_PID, LOG_DAEMON); +} + + +static void +disable_core_dumps (void) +{ +#ifdef HAVE_SETRLIMIT + struct rlimit limit; + + if (getrlimit (RLIMIT_CORE, &limit)) + limit.rlim_max = 0; + limit.rlim_cur = 0; + if( !setrlimit (RLIMIT_CORE, &limit) ) + return 0; + if (errno != EINVAL && errno != ENOSYS) + logit (LOG_ERR, "can't disable core dumps: %s\n", strerror (errno)); +#endif /* HAVE_SETRLIMIT */ +} + + + +static void +print_version (int with_help) +{ + fputs (MYVERSION_LINE "\n" + "Copyright (C) 2006 Free Software Foundation, Inc.\n" + "License GPLv2+: GNU GPL version 2 or later " + "<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n", + stdout); + + if (with_help) + fputs ("\n" + "Usage: " PGM " [OPTIONS] [SOCKETNAME]\n" + "Start Libgcrypt's random number daemon listening" + " on socket SOCKETNAME\n" + "SOCKETNAME defaults to XXX\n" + "\n" + " --no-detach do not deatach from the console\n" + " --version print version of the program and exit\n" + " --help display this help and exit\n" + BUGREPORT_LINE, stdout ); + + exit (0); +} + +static int +print_usage (void) +{ + fputs ("usage: " PGM " [OPTIONS] [SOCKETNAME]\n", stderr); + fputs (" (use --help to display options)\n", stderr); + exit (1); +} + + +int +main (int argc, char **argv) +{ + int no_detach = 0; + gpg_error_t err; + struct sockaddr_un *srvr_addr; + socklen_t addrlen; + int fd; + int rc; + const char *socketname = "/var/run/libgcrypt/S.gcryptrnd"; + + + if (argc) + { + argc--; argv++; + } + while (argc && **argv == '-' && (*argv)[1] == '-') + { + if (!(*argv)[2]) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--version")) + print_version (0); + else if (!strcmp (*argv, "--help")) + print_version (1); + else if (!strcmp (*argv, "--no-detach")) + { + no_detach = 1; + argc--; argv++; + } + else + print_usage (); + } + + if (argc == 1) + socketname = argv[0]; + else if (argc > 1) + print_usage (); + + if (!no_detach) + daemonize (); + + signal (SIGPIPE, SIG_IGN); + + logit (LOG_NOTICE, "started version " VERSION ); + + /* Libgcrypt requires us to register the threading model before we + do anything else with it. Note that this also calls pth_init. We + do the initialization while already running as a daemon to avoid + overhead with double initialization of Libgcrypt. */ + err = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth); + if (err) + { + logit (LOG_CRIT, "can't register GNU Pth with Libgcrypt: %s", + gpg_strerror (err)); + exit (1); + } + + /* Check that the libgcrypt version is sufficient. */ + if (!gcry_check_version (VERSION) ) + { + logit (LOG_CRIT, "libgcrypt is too old (need %s, have %s)", + VERSION, gcry_check_version (NULL) ); + exit (1); + } + + /* Register the logging callback and tell Libcgrypt to put the + random pool into secure memory. */ + gcry_set_log_handler (my_gcry_logger, NULL); + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + /* Obviously we don't want to allow any core dumps. */ + disable_core_dumps (); + + /* Initialize the secure memory stuff which will also drop any extra + privileges we have. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + + /* Register a cleanup handler. */ + atexit (cleanup); + + /* Create and listen on the socket. */ + fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + logit (LOG_CRIT, "can't create socket: %s", strerror (errno)); + exit (1); + } + srvr_addr = gcry_xmalloc (sizeof *srvr_addr); + memset (srvr_addr, 0, sizeof *srvr_addr); + srvr_addr->sun_family = AF_UNIX; + if (strlen (socketname) + 1 >= sizeof (srvr_addr->sun_path)) + { + logit (LOG_CRIT, "socket name `%s' too long", socketname); + exit (1); + } + strcpy (srvr_addr->sun_path, socketname); + addrlen = (offsetof (struct sockaddr_un, sun_path) + + strlen (srvr_addr->sun_path) + 1); + rc = bind (fd, (struct sockaddr*) srvr_addr, addrlen); + if (rc == -1 && errno == EADDRINUSE) + { + remove (socketname); + rc = bind (fd, (struct sockaddr*) srvr_addr, addrlen); + } + if (rc == -1) + { + logit (LOG_CRIT, "error binding socket to `%s': %s", + srvr_addr->sun_path, strerror (errno)); + close (fd); + exit (1); + } + + if (listen (fd, 5 ) == -1) + { + logit (LOG_CRIT, "listen() failed: %s", strerror (errno)); + close (fd); + exit (1); + } + + logit (LOG_INFO, "listening on socket `%s', fd=%d", + srvr_addr->sun_path, fd); + + serve (fd); + close (fd); + + logit (LOG_NOTICE, "stopped version " VERSION ); + return 0; +} + + +/* Send LENGTH bytes of BUFFER to file descriptor FD. Returns 0 on + success or another value on write error. */ +static int +writen (int fd, const void *buffer, size_t length) +{ + while (length) + { + ssize_t n = pth_write (fd, buffer, length); + if (n < 0) + { + logit (LOG_ERR, "connection %d: write error: %s", + fd, strerror (errno)); + return -1; /* write error */ + } + length -= n; + buffer = (const char*)buffer + n; + } + return 0; /* Okay */ +} + + +/* Send an error response back. Returns 0 on success. */ +static int +send_error (int fd, int errcode) +{ + unsigned char buf[2]; + + buf[0] = errcode; + buf[1] = 0; + return writen (fd, buf, 2 ); +} + +/* Send a pong response back. Returns 0 on success or another value + on write error. */ +static int +send_pong (int fd) +{ + return writen (fd, "\x00\x04pong", 6); +} + +/* Send a nonce of size LENGTH back. Return 0 on success. */ +static int +send_nonce (int fd, int length) +{ + unsigned char buf[2+255]; + int rc; + + assert (length >= 0 && length <= 255); + buf[0] = 0; + buf[1] = length; + gcry_create_nonce (buf+2, length); + rc = writen (fd, buf, 2+length ); + wipememory (buf+2, length); + return rc; +} + +/* Send a random of size LENGTH with quality LEVEL back. Return 0 on + success. */ +static int +send_random (int fd, int length, int level) +{ + unsigned char buf[2+255]; + int rc; + + assert (length >= 0 && length <= 255); + assert (level == GCRY_STRONG_RANDOM || level == GCRY_VERY_STRONG_RANDOM); + buf[0] = 0; + buf[1] = length; + /* Note that we don't bother putting the random stuff into secure + memory because this daemon is anyway intended to be run under + root and it is questionable whether the kernel buffers etc. are + equally well protected. */ + gcry_randomize (buf+2, length, level); + rc = writen (fd, buf, 2+length ); + wipememory (buf+2, length); + return rc; +} + +/* Main processing loop for a connection. + + A request is made up of: + + 1 byte Total length of request; must be 3 + 1 byte Command + 0 = Ping + 10 = GetNonce + 11 = GetStrongRandom + 12 = GetVeryStrongRandom + (all other values are reserved) + 1 byte Number of requested bytes. + This is ignored for command Ping. + + A response is made up of: + + 1 byte Error Code + 0 = Everything is fine + 1 = Bad Command + 0xff = Other error. + (For a bad request the connection will simply be closed) + 1 byte Length of data + n byte data + + The requests are read as long as the connection is open. + + + */ +static void +connection_loop (int fd) +{ + unsigned char request[3]; + unsigned char *p; + int nleft, n; + int rc; + + for (;;) + { + for (nleft=3, p=request; nleft > 0; ) + { + n = pth_read (fd, p, nleft); + if (!n && p == request) + return; /* Client terminated connection. */ + if (n <= 0) + { + logit (LOG_ERR, "connection %d: read error: %s", + fd, n? strerror (errno) : "Unexpected EOF"); + return; + } + p += n; + nleft -= n; + } + if (request[0] != 3) + { + logit (LOG_ERR, "connection %d: invalid length (%d) of request", + fd, request[0]); + return; + } + + switch (request[1]) + { + case 0: /* Ping */ + rc = send_pong (fd); + break; + case 10: /* GetNonce */ + rc = send_nonce (fd, request[2]); + break; + case 11: /* GetStrongRandom */ + rc = send_random (fd, request[2], GCRY_STRONG_RANDOM); + break; + case 12: /* GetVeryStrongRandom */ + rc = send_random (fd, request[2], GCRY_VERY_STRONG_RANDOM); + break; + + default: /* Invalid command */ + rc = send_error (fd, 1); + break; + } + if (rc) + break; /* A write error occurred while sending the response. */ + } +} + + + +/* Entry point for a connection's thread. */ +static void * +connection_thread (void *arg) +{ + int fd = (int)arg; + + active_connections++; + logit (LOG_INFO, "connection handler for fd %d started", fd); + + connection_loop (fd); + + close (fd); + logit (LOG_INFO, "connection handler for fd %d terminated", fd); + active_connections--; + + return NULL; +} + + +/* This signal handler is called from the main loop between acepting + connections. It is called on the regular stack, thus no special + caution needs to be taken. It returns true to indicate that the + process should terminate. */ +static int +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + logit (LOG_NOTICE, "SIGHUP received - re-reading configuration"); + break; + + case SIGUSR1: + logit (LOG_NOTICE, "SIGUSR1 received - no action defined"); + break; + + case SIGUSR2: + logit (LOG_NOTICE, "SIGUSR2 received - no action defined"); + break; + + case SIGTERM: + if (!shutdown_pending) + logit (LOG_NOTICE, "SIGTERM received - shutting down ..."); + else + logit (LOG_NOTICE, "SIGTERM received - still %d active connections", + active_connections); + shutdown_pending++; + if (shutdown_pending > 2) + { + logit (LOG_NOTICE, "shutdown forced"); + return 1; + } + break; + + case SIGINT: + logit (LOG_NOTICE, "SIGINT received - immediate shutdown"); + return 1; + + default: + logit (LOG_NOTICE, "signal %d received - no action defined\n", signo); + } + return 0; +} + + + +/* Main server loop. This is called with the FD of the listening + socket. */ +static void +serve (int listen_fd) +{ + pth_attr_t tattr; + pth_event_t ev; + sigset_t sigs; + int signo; + struct sockaddr_un paddr; + socklen_t plen = sizeof (paddr); + int fd; + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "connection"); + + sigemptyset (&sigs); + sigaddset (&sigs, SIGHUP); + sigaddset (&sigs, SIGUSR1); + sigaddset (&sigs, SIGUSR2); + sigaddset (&sigs, SIGINT); + sigaddset (&sigs, SIGTERM); + ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); + + for (;;) + { + if (shutdown_pending) + { + if (!active_connections) + break; /* Ready. */ + + /* Do not accept anymore connections but wait for existing + connections to terminate. */ + signo = 0; + pth_wait (ev); + if (pth_event_occurred (ev) && signo) + if (handle_signal (signo)) + break; /* Stop the loop. */ + continue; + } + + gcry_fast_random_poll (); + fd = pth_accept_ev (listen_fd, (struct sockaddr *)&paddr, &plen, ev); + if (fd == -1) + { + if (pth_event_occurred (ev)) + { + if (handle_signal (signo)) + break; /* Stop the loop. */ + continue; + } + logit (LOG_WARNING, "accept failed: %s - waiting 1s\n", + strerror (errno)); + gcry_fast_random_poll (); + pth_sleep (1); + continue; + } + + if (!pth_spawn (tattr, connection_thread, (void*)fd)) + { + logit (LOG_ERR, "error spawning connection handler: %s\n", + strerror (errno) ); + close (fd); + } + } + + pth_event_free (ev, PTH_FREE_ALL); +} |