#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"
GCRY_THREAD_OPTION_PTH_IMPL;
static int running_detached;
static int shutdown_pending;
static int active_connections;
static void serve (int listen_fd);
#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)
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);
}
static void
my_gcry_logger (void *dummy, int level, const char *format, va_list arg_ptr)
{
(void)dummy;
switch (level)
{
case GCRY_LOG_CONT: level = LOG_INFO ; 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);
}
}
static void
cleanup (void)
{
gcry_control (GCRYCTL_TERM_SECMEM );
}
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);
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
}
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 );
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);
}
if (!gcry_check_version (VERSION) )
{
logit (LOG_CRIT, "libgcrypt is too old (need %s, have %s)",
VERSION, gcry_check_version (NULL) );
exit (1);
}
gcry_set_log_handler (my_gcry_logger, NULL);
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
atexit (cleanup);
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;
}
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;
}
length -= n;
buffer = (const char*)buffer + n;
}
return 0;
}
static int
send_error (int fd, int errcode)
{
unsigned char buf[2];
buf[0] = errcode;
buf[1] = 0;
return writen (fd, buf, 2 );
}
static int
send_pong (int fd)
{
return writen (fd, "\x00\x04pong", 6);
}
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;
}
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;
gcry_randomize (buf+2, length, level);
rc = writen (fd, buf, 2+length );
wipememory (buf+2, length);
return rc;
}
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;
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:
rc = send_pong (fd);
break;
case 10:
rc = send_nonce (fd, request[2]);
break;
case 11:
rc = send_random (fd, request[2], GCRY_STRONG_RANDOM);
break;
case 12:
rc = send_random (fd, request[2], GCRY_VERY_STRONG_RANDOM);
break;
default:
rc = send_error (fd, 1);
break;
}
if (rc)
break;
}
}
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;
}
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;
}
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;
signo = 0;
pth_wait (ev);
if (pth_event_occurred (ev) && signo)
if (handle_signal (signo))
break;
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;
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);
}