/* 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 = (const char *)buffer + n;
    }
  return 0;  /* Okay */
}




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] 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;
}
