/* rndegd.c - interface to the EGD
* Copyright (C) 1999-2012 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, see
*
*/
#include
#ifndef _WIN32
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "egd.h"
#include
#include
#include
#ifdef AF_UNIX
# define LOCAL_SOCKET_TYPE AF_UNIX
#else
# define LOCAL_SOCKET_TYPE AF_LOCAL
#endif
#ifndef offsetof
#define offsetof(type, member) ((size_t) &((type *)0)->member)
#endif
static int egd_socket = -1;
static int
do_write (int fd, void *buf, size_t nbytes)
{
size_t nleft = nbytes;
int nwritten;
while (nleft > 0)
{
nwritten = write (fd, buf, nleft);
if (nwritten < 0)
{
if (errno == EINTR)
continue;
return -1;
}
nleft -= nwritten;
buf = (char *) buf + nwritten;
}
return 0;
}
static int
do_read (int fd, void *buf, size_t nbytes)
{
int n;
size_t nread = 0;
do
{
do
{
n = read (fd, (char *) buf + nread, nbytes);
}
while (n == -1 && errno == EINTR);
if (n == -1)
{
if (nread > 0)
return nread;
else return -1;
}
if (n == 0)
return -1;
nread += n;
nbytes -= n;
}
while (nread < nbytes);
return nread;
}
static const char *egd_names[] = {
"/var/run/egd-pool",
"/dev/egd-pool",
"/etc/egd-pool",
"/etc/entropy",
"/var/run/entropy",
"/dev/entropy",
NULL
};
static const char *
find_egd_name (void)
{
int i = 0;
struct stat st;
do
{
if (stat (egd_names[i], &st) != 0)
continue;
if (st.st_mode & S_IFSOCK)
{ /* found */
return egd_names[i];
}
}
while (egd_names[++i] != NULL);
return NULL;
}
/* Connect to the EGD and return the file descriptor. Return -1 on
error. With NOFAIL set to true, silently fail and return the
error, otherwise print an error message and die. */
int
_rndegd_connect_socket (void)
{
int fd;
const char *name;
struct sockaddr_un addr;
int addr_len;
if (egd_socket != -1)
{
close (egd_socket);
egd_socket = -1;
}
name = find_egd_name ();
if (name == NULL)
{
_gnutls_debug_log ("Could not detect an egd device.\n");
return -1;
}
if (strlen (name) + 1 >= sizeof addr.sun_path)
{
_gnutls_debug_log ("EGD socketname is too long\n");
return -1;
}
memset (&addr, 0, sizeof addr);
addr.sun_family = LOCAL_SOCKET_TYPE;
_gnutls_str_cpy (addr.sun_path, sizeof(addr.sun_path), name);
addr_len = (offsetof (struct sockaddr_un, sun_path)
+ strlen (addr.sun_path));
fd = socket (LOCAL_SOCKET_TYPE, SOCK_STREAM, 0);
if (fd == -1)
{
_gnutls_debug_log ("can't create unix domain socket: %s\n",
strerror (errno));
return -1;
}
else if (connect (fd, (struct sockaddr *) &addr, addr_len) == -1)
{
_gnutls_debug_log ("can't connect to EGD socket `%s': %s\n",
name, strerror (errno));
close (fd);
fd = -1;
}
if (fd != -1)
egd_socket = fd;
return fd;
}
/****************
* Note: We always use the highest level.
* To boost the performance we may want to add some
* additional code for level 1
*
* Using a level of 0 should never block and better add nothing
* to the pool. So this is just a dummy for EGD.
*/
int
_rndegd_read (int *fd, void *_output, size_t _length)
{
ssize_t n;
uint8_t buffer[256 + 2];
int nbytes;
int do_restart = 0;
unsigned char *output = _output;
ssize_t length = (ssize_t)_length;
if (!length)
return 0;
restart:
if (*fd == -1 || do_restart)
*fd = _rndegd_connect_socket ();
do_restart = 0;
nbytes = length < 255 ? length : 255;
/* First time we do it with a non blocking request */
buffer[0] = 1; /* non blocking */
buffer[1] = nbytes;
if (do_write (*fd, buffer, 2) == -1)
_gnutls_debug_log ("can't write to the EGD: %s\n", strerror (errno));
n = do_read (*fd, buffer, 1);
if (n == -1)
{
_gnutls_debug_log ("read error on EGD: %s\n", strerror (errno));
do_restart = 1;
goto restart;
}
n = buffer[0];
if (n)
{
n = do_read (*fd, buffer, n);
if (n == -1)
{
_gnutls_debug_log ("read error on EGD: %s\n", strerror (errno));
do_restart = 1;
goto restart;
}
if (n > length)
{
_gnutls_debug_log ("read error on EGD: returned more bytes!\n");
n = length;
}
memcpy (output, buffer, n);
output += n;
length -= n;
}
while (length)
{
nbytes = length < 255 ? length : 255;
buffer[0] = 2; /* blocking */
buffer[1] = nbytes;
if (do_write (*fd, buffer, 2) == -1)
_gnutls_debug_log ("can't write to the EGD: %s\n", strerror (errno));
n = do_read (*fd, buffer, nbytes);
if (n == -1)
{
_gnutls_debug_log ("read error on EGD: %s\n", strerror (errno));
do_restart = 1;
goto restart;
}
if (n > length)
{
_gnutls_debug_log ("read error on EGD: returned more bytes!\n");
n = length;
}
memcpy (output, buffer, n);
output += n;
length -= n;
}
return _length; /* success */
}
#endif