Wrote random daemon and client.
authorWerner Koch <wk@gnupg.org>
Wed, 15 Mar 2006 11:46:50 +0000 (11:46 +0000)
committerWerner Koch <wk@gnupg.org>
Wed, 15 Mar 2006 11:46:50 +0000 (11:46 +0000)
src/ChangeLog
src/Makefile.am
src/gcryptrnd.c [new file with mode: 0644]
src/getrandom.c [new file with mode: 0644]

index 76c4cfa..423ee18 100644 (file)
@@ -1,3 +1,11 @@
+2006-03-15  Werner Koch  <wk@g10code.com>
+
+       * getrandom.c: New.
+
+2006-03-14  Werner Koch  <wk@g10code.com>
+
+       * gcryptrnd.c: New.
+
 2006-03-10  Werner Koch  <wk@g10code.com>
 
        * gcrypt.h: Add GCRY_MD_SHA224.
index c995bfd..5d9941a 100644 (file)
@@ -25,6 +25,8 @@ m4data_DATA = libgcrypt.m4
 include_HEADERS = gcrypt.h gcrypt-module.h
 
 lib_LTLIBRARIES = libgcrypt.la
+sbin_PROGRAMS = gcryptrnd
+bin_PROGRAMS = getrandom
 
 if HAVE_LD_VERSION_SCRIPT
   libgcrypt_version_script_cmd = -Wl,--version-script=$(srcdir)/libgcrypt.vers
@@ -45,3 +47,10 @@ libgcrypt_la_DEPENDENCIES = ../cipher/libcipher.la ../mpi/libmpi.la \
        $(srcdir)/libgcrypt.vers
 libgcrypt_la_LIBADD = ../cipher/libcipher.la ../mpi/libmpi.la \
        @LTLIBOBJS@ @GPG_ERROR_LIBS@
+
+gcryptrnd_SOURCES = gcryptrnd.c
+gcryptrnd_CFLAGS = $(GPG_ERROR_CFLAGS) $(PTH_CFLAGS)
+gcryptrnd_LDADD = ../src/libgcrypt.la $(PTH_LIBS)
+
+getrandom_SOURCES = getrandom.c
+
diff --git a/src/gcryptrnd.c b/src/gcryptrnd.c
new file mode 100644 (file)
index 0000000..5a645bb
--- /dev/null
@@ -0,0 +1,678 @@
+/* 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);
+
+
+
+\f
+
+/* 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 fucntions prints to the stderro 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)
+{
+  /* 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"
+         "This program comes with ABSOLUTELY NO WARRANTY.\n"
+         "This is free software, and you are welcome to redistribute it\n"
+         "under certain conditions. See the file COPYING for details.\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 += 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 occured 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);
+}
+
diff --git a/src/getrandom.c b/src/getrandom.c
new file mode 100644 (file)
index 0000000..cf31eeb
--- /dev/null
@@ -0,0 +1,326 @@
+/* getrandom.c - Libgcrypt Random Number client
+ * Copyright (C) 2006 Free Software Foundation, Inc.
+ *
+ * Getrandom 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.
+ *
+ * Getrandom 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.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define PGM "getrandom"
+#define MYVERSION_LINE PGM " (Libgcrypt) " VERSION
+#define BUGREPORT_LINE "\nReport bugs to <bug-libgcrypt@gnupg.org>.\n"
+
+
+static void
+logit (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, format) ;
+  fputs (PGM ": ", stderr);
+  vfprintf (stderr, format, arg_ptr);
+  putc ('\n', stderr);
+  va_end (arg_ptr);
+}
+
+
+/* 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;
+      
+      do
+        n = write (fd, buffer, length);
+      while (n < 0 && errno == EINTR);
+      if (n < 0)
+         {
+           logit ("write error: %s", strerror (errno));
+           return -1; /* write error */
+         }
+      length -= n;
+      buffer += n;
+    }
+  return 0;  /* Okay */
+}
+
+
+
+
+static void
+print_version (int with_help)
+{
+  fputs (MYVERSION_LINE "\n"
+         "Copyright (C) 2006 Free Software Foundation, Inc.\n"
+         "This program comes with ABSOLUTELY NO WARRANTY.\n"
+         "This is free software, and you are welcome to redistribute it\n"
+         "under certain conditions. See the file COPYING for details.\n",
+         stdout);
+        
+  if (with_help)
+    fputs ("\n"
+           "Usage: " PGM " [OPTIONS] NBYTES\n"
+           "Connect to libgcrypt's random number daemon and "
+           "return random numbers"
+           "\n"
+           "  --nonce       Return weak random suitable for a nonce\n"
+           "  --very-strong Return very strong random\n"
+           "  --ping        Send a ping\n"
+           "  --socket NAME Name of sockket to connect to\n"        
+           "  --hex         Return result as a hex dump\n"
+           "  --verbose     Show what we are doing\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] NBYTES\n", stderr);
+  fputs ("       (use --help to display options)\n", stderr);
+  exit (1);
+}
+
+
+int 
+main (int argc, char **argv)
+{
+  struct sockaddr_un *srvr_addr;
+  socklen_t addrlen;
+  int fd;
+  int rc;
+  unsigned char buffer[300];
+  int nleft, nread;
+  const char *socketname = "/var/run/libgcrypt/S.gcryptrnd";
+  int do_ping = 0;
+  int get_nonce = 0;
+  int get_very_strong = 0;
+  int req_nbytes, nbytes, n;
+  int verbose = 0;
+  int fail = 0;
+  int do_hex = 0;
+  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, "--socket") && argc > 1 )
+        {
+          argc--; argv++;
+          socketname = *argv;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--nonce"))
+        {
+          argc--; argv++;
+          get_nonce = 1;
+        }
+      else if (!strcmp (*argv, "--very-strong"))
+        {
+          argc--; argv++;
+          get_very_strong = 1;
+        }
+      else if (!strcmp (*argv, "--ping"))
+        {
+          argc--; argv++;
+          do_ping = 1;
+        }
+      else if (!strcmp (*argv, "--hex"))
+        {
+          argc--; argv++;
+          do_hex = 1;
+        }
+      else if (!strcmp (*argv, "--verbose"))
+        {
+          argc--; argv++;
+          verbose = 1;
+        }
+      else
+        print_usage ();
+    }          
+
+  if (!argc && do_ping)
+    ; /* This is allowed. */
+  else if (argc != 1)
+    print_usage ();
+  req_nbytes = argc? atoi (*argv) : 0;
+  
+  if (req_nbytes < 0)
+    print_usage ();
+
+  /* Create a socket. */
+  fd = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (fd == -1)
+    {
+      logit ("can't create socket: %s", strerror (errno));
+      exit (1);
+    }
+  srvr_addr = malloc (sizeof *srvr_addr); 
+  if (!srvr_addr)
+    {
+      logit ("malloc failed: %s", strerror (errno));
+      exit (1);
+    }
+  memset (srvr_addr, 0, sizeof *srvr_addr);
+  srvr_addr->sun_family = AF_UNIX;
+  if (strlen (socketname) + 1 >= sizeof (srvr_addr->sun_path))
+    {
+      logit ("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 = connect (fd, (struct sockaddr*) srvr_addr, addrlen);
+  if (rc == -1)
+    {
+      logit ("error connecting socket `%s': %s",
+             srvr_addr->sun_path, strerror (errno));
+      close (fd);
+      exit (1);
+    }
+
+  do
+    {
+      nbytes = req_nbytes > 255? 255 : req_nbytes;
+      req_nbytes -= nbytes;
+
+      buffer[0] = 3;
+      if (do_ping)
+        buffer[1] = 0;
+      else if (get_nonce)
+        buffer[1] = 10;
+      else if (get_very_strong)
+        buffer[1] = 12;
+      else
+        buffer[1] = 11;
+      buffer[2] = nbytes;
+      if (writen (fd, buffer, 3))
+        fail = 1;
+      else
+        {
+          for (nleft=2, nread=0; nleft > 0; )
+            {
+              do 
+                n = read (fd, buffer+nread, nleft);
+              while (n < 0 && errno == EINTR);
+              if (n < 0)
+                {
+                  logit ("read error: %s", strerror (errno));
+                  exit (1);
+                }
+              nleft -= n;
+              nread += n;
+              if (nread && buffer[0])
+                {
+                  logit ("server returned error code %d", buffer[0]);
+                  exit (1);
+                }
+            }
+          if (verbose)
+            logit ("received response with %d bytes of data", buffer[1]);
+          if (buffer[1] < nbytes)
+            {
+              logit ("warning: server returned less bytes than requested");
+              fail = 1;
+            }
+          else if (buffer[1] > nbytes && !do_ping)
+            {
+              logit ("warning: server returned more bytes than requested");
+              fail = 1;
+            }
+          nbytes = buffer[1];
+          if (nbytes > sizeof buffer)
+            {
+              logit ("buffer too short to receive data");
+              exit (1);
+            }
+
+          for (nleft=nbytes, nread=0; nleft > 0; )
+            {
+              do 
+                n = read (fd, buffer+nread, nleft);
+              while (n < 0 && errno == EINTR);
+              if (n < 0)
+                {
+                  logit ("read error: %s", strerror (errno));
+                  exit (1);
+                }
+              nleft -= n;
+              nread += n;
+            }
+
+          if (do_hex)
+            {
+              for (n=0; n < nbytes; n++)
+                {
+                  if (!n)
+                    ;
+                  else if (!(n % 16))
+                    putchar ('\n');
+                  else
+                    putchar (' ');
+                  printf ("%02X", buffer[n]);
+                }
+              if (nbytes)
+                putchar ('\n');
+            }
+          else
+            {
+              if (fwrite (buffer, nbytes, 1, stdout) != 1)
+                {
+                  logit ("error writing to stdout: %s", strerror (errno));
+                  fail = 1;
+                }
+            }
+        }
+    }
+  while (!fail && req_nbytes);
+
+  close (fd);
+  free (srvr_addr);
+  return fail? 1 : 0;
+}
+