summary refs log tree commit diff stats
path: root/libotr/libgcrypt-1.8.7/src/gcryptrnd.c
diff options
context:
space:
mode:
Diffstat (limited to 'libotr/libgcrypt-1.8.7/src/gcryptrnd.c')
-rw-r--r--libotr/libgcrypt-1.8.7/src/gcryptrnd.c680
1 files changed, 680 insertions, 0 deletions
diff --git a/libotr/libgcrypt-1.8.7/src/gcryptrnd.c b/libotr/libgcrypt-1.8.7/src/gcryptrnd.c
new file mode 100644
index 0000000..b13931b
--- /dev/null
+++ b/libotr/libgcrypt-1.8.7/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);
+}