/* 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);
}
