/* random-daemon.c  - Access to the external random daemon
 * Copyright (C) 2006  Free Software Foundation, Inc.
 *
 * This file is part of Libgcrypt.
 *
 * Libgcrypt is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * Libgcrypt 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

/*
   The functions here are used by random.c to divert calls to an
   external random number daemon.  The actual daemon we use is
   gcryptrnd.  Such a daemon is useful to keep a persistent pool in
   memory over invocations of a single application and to allow
   prioritizing access to the actual entropy sources.  The drawback is
   that we need to use IPC (i.e. unix domain socket) to convey
   sensitive data.
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <unistd.h>

#include "g10lib.h"
#include "random.h"



/* This is default socket name we use in case the provided socket name
   is NULL.  */
#define RANDOM_DAEMON_SOCKET "/var/run/libgcrypt/S.gcryptrnd"

/* The lock serializing access to the daemon.  */
GPGRT_LOCK_DEFINE (daemon_lock);

/* The socket connected to the daemon.  */
static int daemon_socket = -1;

/* Creates a socket connected to the daemon.  On success, store the
   socket fd in *SOCK.  Returns error code.  */
static gcry_error_t
connect_to_socket (const char *socketname, int *sock)
{
  struct sockaddr_un *srvr_addr;
  socklen_t addrlen;
  gcry_error_t err;
  int fd;
  int rc;

  srvr_addr = NULL;

  /* Create a socket. */
  fd = socket (AF_UNIX, SOCK_STREAM, 0);
  if (fd == -1)
    {
      log_error ("can't create socket: %s\n", strerror (errno));
      err = gcry_error_from_errno (errno);
      goto out;
    }

  /* Set up address.  */
  srvr_addr = gcry_malloc (sizeof *srvr_addr);
  if (! srvr_addr)
    {
      log_error ("malloc failed: %s\n", strerror (errno));
      err = gcry_error_from_errno (errno);
      goto out;
    }
  memset (srvr_addr, 0, sizeof *srvr_addr);
  srvr_addr->sun_family = AF_UNIX;
  if (strlen (socketname) + 1 >= sizeof (srvr_addr->sun_path))
    {
      log_error ("socket name `%s' too long\n", socketname);
      err = gcry_error (GPG_ERR_ENAMETOOLONG);
      goto out;
    }
  strcpy (srvr_addr->sun_path, socketname);
  addrlen = (offsetof (struct sockaddr_un, sun_path)
             + strlen (srvr_addr->sun_path) + 1);

  /* Connect socket.  */
  rc = connect (fd, (struct sockaddr *) srvr_addr, addrlen);
  if (rc == -1)
    {
      log_error ("error connecting socket `%s': %s\n",
		 srvr_addr->sun_path, strerror (errno));
      err = gcry_error_from_errno (errno);
      goto out;
    }

  err = 0;

 out:

  gcry_free (srvr_addr);
  if (err)
    {
      close (fd);
      fd = -1;
    }
  *sock = fd;

  return err;
}


/* Initialize basics of this module. This should be viewed as a
   constructor to prepare locking. */
void
_gcry_daemon_initialize_basics (void)
{
  /* Not anymore required.  */
}



/* 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)
{
  ssize_t n;

  while (length)
    {
      do
        n = ath_write (fd, buffer, length);
      while (n < 0 && errno == EINTR);
      if (n < 0)
         {
           log_error ("write error: %s\n", strerror (errno));
           return -1; /* write error */
         }
      length -= n;
      buffer = (const char*)buffer + n;
    }
  return 0;  /* Okay */
}

static int
readn (int fd, void *buf, size_t buflen, size_t *ret_nread)
{
  size_t nleft = buflen;
  int nread;
  char *p;

  p = buf;
  while (nleft > 0)
    {
      nread = ath_read (fd, buf, nleft);
      if (nread < 0)
        {
          if (nread == EINTR)
            nread = 0;
          else
            return -1;
        }
      else if (!nread)
        break; /* EOF */
      nleft -= nread;
      buf = (char*)buf + nread;
    }
  if (ret_nread)
    *ret_nread = buflen - nleft;
  return 0;
}

/* This functions requests REQ_NBYTES from the daemon.  If NONCE is
   true, the data should be suited for a nonce.  If NONCE is FALSE,
   data of random level LEVEL will be generated.  The retrieved random
   data will be stored in BUFFER.  Returns error code.  */
static gcry_error_t
call_daemon (const char *socketname,
             void *buffer, size_t req_nbytes, int nonce,
	     enum gcry_random_level level)
{
  static int initialized;
  unsigned char buf[255];
  gcry_error_t err = 0;
  size_t nbytes;
  size_t nread;
  int rc;

  if (!req_nbytes)
    return 0;

  gpgrt_lock_lock (&daemon_lock);

  /* Open the socket if that has not been done. */
  if (!initialized)
    {
      initialized = 1;
      err = connect_to_socket (socketname ? socketname : RANDOM_DAEMON_SOCKET,
			       &daemon_socket);
      if (err)
        {
          daemon_socket = -1;
          log_info ("not using random daemon\n");
          gpgrt_lock_unlock (&daemon_lock);
          return err;
        }
    }

  /* Check that we have a valid socket descriptor. */
  if ( daemon_socket == -1 )
    {
      gpgrt_lock_unlock (&daemon_lock);
      return gcry_error (GPG_ERR_INTERNAL);
    }


  /* Do the real work.  */

  do
    {
      /* Process in chunks.  */
      nbytes = req_nbytes > sizeof (buf) ? sizeof (buf) : req_nbytes;
      req_nbytes -= nbytes;

      /* Construct request.  */
      buf[0] = 3;
      if (nonce)
	buf[1] = 10;
      else if (level == GCRY_VERY_STRONG_RANDOM)
	buf[1] = 12;
      else if (level == GCRY_STRONG_RANDOM)
	buf[1] = 11;
      buf[2] = nbytes;

      /* Send request.  */
      rc = writen (daemon_socket, buf, 3);
      if (rc == -1)
	{
	  err = gcry_error_from_errno (errno);
	  break;
	}

      /* Retrieve response.  */

      rc = readn (daemon_socket, buf, 2, &nread);
      if (rc == -1)
	{
	  err = gcry_error_from_errno (errno);
	  log_error ("read error: %s\n", _gcry_strerror (err));
	  break;
	}
      if (nread && buf[0])
	{
	  log_error ("random daemon returned error code %d\n", buf[0]);
	  err = gcry_error (GPG_ERR_INTERNAL); /* ? */
	  break;
	}
      if (nread != 2)
	{
	  log_error ("response too small\n");
	  err = gcry_error (GPG_ERR_PROTOCOL_VIOLATION); /* ? */
	  break;
	}

      /*      if (1)*/			/* Do this in verbose mode? */
      /*	log_info ("received response with %d bytes of data\n", buf[1]);*/

      if (buf[1] < nbytes)
	{
	  log_error ("error: server returned less bytes than requested\n");
	  err = gcry_error (GPG_ERR_PROTOCOL_VIOLATION); /* ? */
	  break;
	}
      else if (buf[1] > nbytes)
	{
	  log_error ("warning: server returned more bytes than requested\n");
	  err = gcry_error (GPG_ERR_PROTOCOL_VIOLATION); /* ? */
	  break;
	}

      assert (nbytes <= sizeof (buf));

      rc = readn (daemon_socket, buf, nbytes, &nread);
      if (rc == -1)
	{
	  err = gcry_error_from_errno (errno);
	  log_error ("read error: %s\n", _gcry_strerror (err));
	  break;
	}

      if (nread != nbytes)
	{
	  log_error ("too little random data read\n");
	  err = gcry_error (GPG_ERR_INTERNAL);
	  break;
	}

      /* Successfuly read another chunk of data.  */
      memcpy (buffer, buf, nbytes);
      buffer = ((char *) buffer) + nbytes;
    }
  while (req_nbytes);

  gpgrt_lock_unlock (&daemon_lock);

  return err;
}

/* Internal function to fill BUFFER with LENGTH bytes of random.  We
   support GCRY_STRONG_RANDOM and GCRY_VERY_STRONG_RANDOM here.
   Return 0 on success. */
int
_gcry_daemon_randomize (const char *socketname,
                        void *buffer, size_t length,
                        enum gcry_random_level level)
{
  gcry_error_t err;

  err = call_daemon (socketname, buffer, length, 0, level);

  return err ? -1 : 0;
}

/* END */
