summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Xu (Hello71) <alex_y_xu@yahoo.ca>2018-08-08 15:16:14 -0400
committerAlex Xu (Hello71) <alex_y_xu@yahoo.ca>2018-08-08 15:16:14 -0400
commit0183098f9cb37a5389b8ff19dee98a4293752ce6 (patch)
tree22b8fb52d009192427d48f8c8de1e5831b30521d
downloadrandom-seed-0183098f9cb37a5389b8ff19dee98a4293752ce6.tar.xz
random-seed-0183098f9cb37a5389b8ff19dee98a4293752ce6.zip
Initial commit
-rw-r--r--.gitignore12
-rw-r--r--INSTALL10
-rw-r--r--Makefile.in41
-rw-r--r--README13
-rwxr-xr-xautogen.sh8
-rw-r--r--configure.ac24
-rw-r--r--doc/file-format-and-process.md30
-rw-r--r--musl-libgen-c.h53
-rw-r--r--random-seed.c561
-rw-r--r--readall.c82
-rw-r--r--readall.h14
-rw-r--r--sha2.c204
-rw-r--r--sha2.h69
-rw-r--r--util.c93
-rw-r--r--util.h41
15 files changed, 1255 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0734561
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+/aclocal.m4
+/autom4te.cache
+/config.h.in
+/config.log
+/config.status
+/configure
+/Makefile
+
+/random-seed
+/random-seed.test
+*.o
+*.d
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..11f3a22
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,10 @@
+Use ./configure; make; sudo make test; sudo make install.
+
+If building from git, run ./autogen.sh first.
+
+Tests must be run as root in order to access RNDADDENTROPY.
+
+On systemd, to set random-seed as your random seed provider, execute:
+
+ systemctl enable random-seed
+ systemctl mask systemd-random-seed
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..a86c2f9
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,41 @@
+# @configure_input@
+
+datarootdir = @datarootdir@
+sbindir = @sbindir@
+sysconfdir = @sysconfdir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+CPPFLAGS = @CPPFLAGS@
+CFLAGS = -Wall -Wextra -pedantic @SYSTEMD_CFLAGS@ @CFLAGS@ -MD -MP -UNDEBUG -include @abs_top_builddir@/config.h
+LDFLAGS = @LDFLAGS@
+LDLIBS = @SYSTEMD_LIBS@
+
+SRC ::= random-seed.c sha2.c util.c readall.c
+OBJ ::= $(SRC:.c=.o)
+DEP ::= $(SRC:.c=.d)
+TEST_FILE ::= random-seed.test
+
+all: random-seed
+
+random-seed: $(OBJ)
+
+install: random-seed
+ install -D -m755 random-seed $(sbindir)/random-seed
+ install -D -m644 random-seed.8 $(mandir)/man8/random-seed.8
+ifneq (@systemdsystemunitdir@,)
+ install -D -m644 random-seed.service @systemdsystemunitdir@/random-seed.service
+endif
+
+test: $(TEST_FILE)
+
+$(TEST_FILE): random-seed
+ ./random-seed save $(TEST_FILE)
+ ./random-seed load $(TEST_FILE)
+
+clean:
+ $(RM) random-seed $(OBJ) $(DEP) $(TEST_FILE)
+
+-include $(DEP)
+
+.PHONY: all install test clean
+.INTERMEDIATE: $(TEST_FILE)
diff --git a/README b/README
new file mode 100644
index 0000000..fbb9b56
--- /dev/null
+++ b/README
@@ -0,0 +1,13 @@
+random-seed is a random seed management program. In contrast with other random
+seed implementations, random-seed will credit the random seed to the kernel
+entropy count. It attempts to prevent inadvertent random seed sharing by
+checking that certain system identifiers, such as the machine ID and filesystem
+ID have not changed between a save and load. If these identifiers do not
+match, random-seed will still load the random seed, but will not credit the
+entropy.
+
+It is my understanding that other operating systems are either not commonly imaged (e.g. BSDs) or have official tools for system image preparation (e.g. sysprep for Windows). Therefore, random-seed is Linux specific. However, it should be reasonably easy to port by simply adjusting the paths and changing getrandom to /dev/random.
+
+random-seed requires GNU make to compile.
+
+random-seed requires Linux 3.11 or higher supporting the getrandom(2) system call.
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..81f2919
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -e
+
+autoheader &
+aclocal
+autoconf
+wait
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..386fa5e
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,24 @@
+AC_INIT(random-seed, 0.1)
+AC_LANG(C)
+AC_CONFIG_HEADERS(config.h)
+AC_PROG_CC
+
+CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L"
+CFLAGS="$CFLAGS -std=c99"
+
+PKG_HAVE_DEFINE_WITH_MODULES(SYSTEMD, libsystemd, , , [use libsystemd to get machine id])
+AS_IF([test "$with_systemd" = yes],
+ [PKG_CHECK_VAR(systemdsystemunitdir, systemd, systemdsystemunitdir)])
+
+AC_ARG_ENABLE(debug,
+ AC_HELP_STRING(--enable-debug, [enable debug output [default=no]]),
+ [enable_debug=$enableval],
+ [enable_debug=no]
+ )
+AS_CASE([$enable_debug],
+ [yes], [AC_DEFINE(DEBUG, [], [enable debug output])],
+ [no], [],
+ [AC_MSG_ERROR([invalid argument to --enable-debug])]
+)
+
+AC_OUTPUT(Makefile)
diff --git a/doc/file-format-and-process.md b/doc/file-format-and-process.md
new file mode 100644
index 0000000..6ffc067
--- /dev/null
+++ b/doc/file-format-and-process.md
@@ -0,0 +1,30 @@
+The random-seed format consists of:
+
+1. 512 bytes of random seed data for compatibility with other random seed
+ implementations
+2. The magic string "RANDOM SEED FILE VERSION 1".
+3. A series of line delimited commands with space delimited arguments.
+
+Comments are not supported.
+
+# Hashing
+In an attempt to improve privacy, device IDs are hashed with SHA256(random-data
+|| ID) where || denotes concatenation and random-data is the 512 bytes of
+random data at the start of the file.
+
+# Commands
+
+## salt
+Set the salt for the following commands to the argument. This must be the
+first command.
+
+## machine-id
+Check that the contents of `/etc/machine-id`, when hashed, matches the
+argument.
+
+## fs-id
+Check that calling statfs(2) on the random seed file returns a `f_fsid` that
+when hashed, matches the argument.
+
+## done
+End of mandatory commands.
diff --git a/musl-libgen-c.h b/musl-libgen-c.h
new file mode 100644
index 0000000..99c90ee
--- /dev/null
+++ b/musl-libgen-c.h
@@ -0,0 +1,53 @@
+/*
+ * Copied from musl.
+ *
+ * Copyright © 2005-2014 Rich Felker, et al.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MUSL_LIBGEN_H
+#define MUSL_LIBGEN_H
+
+#include <string.h>
+
+static inline char *mybasename(char *s)
+{
+ size_t i;
+ if (!s || !*s) return ".";
+ i = strlen(s)-1;
+ for (; i&&s[i]=='/'; i--) s[i] = 0;
+ for (; i&&s[i-1]!='/'; i--);
+ return s+i;
+}
+
+static inline char *mydirname(char *s)
+{
+ size_t i;
+ if (!s || !*s) return ".";
+ i = strlen(s)-1;
+ for (; s[i]=='/'; i--) if (!i) return "/";
+ for (; s[i]!='/'; i--) if (!i) return ".";
+ for (; s[i]=='/'; i--) if (!i) return "/";
+ s[i+1] = 0;
+ return s;
+}
+
+#endif
diff --git a/random-seed.c b/random-seed.c
new file mode 100644
index 0000000..7a0527a
--- /dev/null
+++ b/random-seed.c
@@ -0,0 +1,561 @@
+/* Copyright 2018 Alex Xu (aka Hello71, alxu)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* we don't use enough CPU time, better be safe than sorry */
+#ifdef NDEBUG
+#warning "NDEBUG is always disabled for security"
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#ifndef WITHOUT_SYSTEMD
+#include <systemd/sd-id128.h>
+#endif
+
+#include "musl-libgen-c.h"
+#include "readall.h"
+#include "util.h"
+
+// musl forbids include/linux
+#define RNDADDENTROPY _IOW( 'R', 0x03, int [2] )
+
+/* 3 hours */
+#define DAEMONIZE_SLEEP_TIME (3*60*60)
+
+struct rand_pool_info_ {
+ int entropy_count;
+ int buf_size;
+ uint32_t buf[RAND_POOL_SIZE / sizeof(uint32_t)];
+};
+
+static const char MAGIC[] = "RANDOM SEED FILE VERSION 1\n";
+
+static sig_atomic_t sigint = 0;
+static sig_atomic_t sigterm = 0;
+
+static inline void usage() {
+ puts("usage: random-seed MODE FILE");
+ puts("see random-seed(8) for more information.");
+}
+
+static size_t get_machine_id(unsigned char **machine_id) {
+#ifdef HAVE_SYSTEMD
+ sd_id128_t raw_machine_id;
+ int r = sd_id128_get_machine(&raw_machine_id);
+ if (r) {
+ fprintf(stderr, "error getting machine id: %s\n", strerror(-r));
+ *machine_id = NULL;
+ return 0;
+ }
+ *machine_id = malloc(33);
+ /* convert to string to be compatible with non-hex machine IDs */
+ sd_id128_to_string(raw_machine_id, (char *)*machine_id);
+ /* match /etc/machine-id format */
+ (*machine_id)[32] = '\n';
+
+ return 33;
+#else
+ char *machine_id;
+ ssize_t machine_id_len;
+
+ const char *etc_machine_id = SYSCONFDIR "/machine-id";
+ const char *var_lib_dbus_machine_id = LOCALSTATEDIR "/lib/dbus/machine-id";
+ FILE *machine_id_file = fopen(etc_machine_id, "r");
+ if (!machine_id_file) {
+ if (errno != ENOENT)
+ fprintf(stderr, "error opening %s: %s, trying %s\n",
+ etc_machine_id, strerror(errno), var_lib_dbus_machine_id);
+ machine_id_file = fopen(var_lib_dbus_machine_id, "r");
+ if (!machine_id_file) {
+ fprintf(stderr, "couldn't open any machine-id file, last error: "
+ "%s; tried: %s, %s\n", strerror(errno), etc_machine_id,
+ var_lib_dbus_machine_id);
+ *machine_id = NULL;
+ return 0;
+ }
+ }
+
+ if (readall(machine_id_file, machine_id, &machine_id_len) != READALL_OK) {
+ fputs("error reading machine id file\n", stderr);
+ *machine_id = NULL;
+ return 0;
+ }
+
+ return machine_id_len;
+#endif
+}
+
+static bool get_machine_id_hash(const unsigned char salt[static SALT_LEN], unsigned char machine_id_digest[static HASH_LEN]) {
+ static unsigned char *c_machine_id;
+ static size_t c_machine_id_len;
+ if (!c_machine_id)
+ c_machine_id_len = get_machine_id(&c_machine_id);
+ if (!c_machine_id_len) {
+ free(c_machine_id);
+ c_machine_id = NULL;
+ return false;
+ }
+ unsigned char c_machine_id_digest[HASH_LEN];
+ hash(salt, c_machine_id_digest, c_machine_id, c_machine_id_len);
+ memcpy(machine_id_digest, c_machine_id_digest, HASH_LEN);
+ return true;
+}
+
+static inline bool get_fs_id_hash(const unsigned char salt[static SALT_LEN], unsigned char fsid_digest[static HASH_LEN], int seed_fd) {
+ struct statfs statfs_buf;
+ if (fstatfs(seed_fd, &statfs_buf) == -1) {
+ fprintf(stderr, "error statfs seed file: %s, "
+ "disabling entropy credit\n", strerror(errno));
+ return false;
+ }
+ hash(salt, fsid_digest, &statfs_buf.f_fsid, sizeof(statfs_buf.f_fsid));
+ return true;
+}
+
+static bool run_seed_file_cmd(const char *cmd, const unsigned char *salt, FILE *seed_file) {
+ char *arg;
+ if (streq(cmd, "machine-id")) {
+ arg = strtok(NULL, " \t");
+ if (!arg) {
+ fputs("error parsing seed file: expected argument "
+ "to 'machine-id'\n", stderr);
+ return false;
+ }
+ unsigned char machine_id_hash[HASH_LEN];
+ if (!get_machine_id_hash(salt, machine_id_hash)) {
+ fputs("error getting machine id hash, disabling entropy credit\n",
+ stderr);
+ return false;
+ }
+
+ if (!hash_match(machine_id_hash, arg)) {
+ fputs("machine-id does not match, disabling entropy credit\n",
+ stderr);
+ return false;
+ }
+ return true;
+ }
+
+ if (streq(cmd, "fs-id")) {
+ arg = strtok(NULL, " \t");
+ if (!arg) {
+ fputs("error parsing seed file: expected argument to 'fs-id'\n", stderr);
+ return false;
+ }
+ unsigned char fs_id_hash[HASH_LEN];
+ if (!get_fs_id_hash(salt, fs_id_hash, fileno(seed_file))) {
+ fputs("error getting fs id hash, disabling entropy credit\n", stderr);
+ return false;
+ }
+
+ if (!hash_match(fs_id_hash, arg)) {
+ fputs("fs id does not match, disabling entropy credit\n", stderr);
+ return false;
+ }
+ return true;
+ }
+
+ fprintf(stderr, "error parsing seed file: unsupported command: %s\n", cmd);
+ return false;
+}
+
+static bool load(FILE *seed_file) {
+ bool credit_entropy = true;
+
+ struct rand_pool_info_ rpi = {
+ .entropy_count = RAND_POOL_SIZE * 8,
+ .buf_size = RAND_POOL_SIZE,
+ .buf = { 0 }
+ };
+
+ uint64_t linenum = 0;
+ char *line = NULL;
+ size_t n = 0;
+ bool done = false;
+
+ if (fread(&rpi.buf, 1, RAND_POOL_SIZE, seed_file) != RAND_POOL_SIZE) {
+ if (feof(seed_file)) {
+ fputs("premature EOF on seed file\n", stderr);
+ } else if (ferror(seed_file)) {
+ fputs("error reading from seed file\n", stderr);
+ } else {
+ fputs("short read from seed file\n", stderr);
+ }
+ return false;
+ }
+
+ unsigned char *salt = (unsigned char *)rpi.buf;
+
+ while (1) {
+ errno = 0;
+ ssize_t line_length = getline(&line, &n, seed_file);
+ if (line_length == -1) {
+ if (errno) {
+ perror("error reading from seed file");
+ credit_entropy = false;
+ }
+ break;
+ }
+
+ linenum++;
+
+ if (linenum == 1) {
+ if (streq(line, MAGIC)) {
+ continue;
+ } else {
+ fputs("error parsing seed file: invalid magic\n", stderr);
+ credit_entropy = false;
+ break;
+ }
+ }
+
+ char *nul = memchr(line, '\0', line_length);
+ if (nul) {
+ fprintf(stderr, "error parsing seed file: encountered NUL byte in commands line %zu char %zu\n", linenum, nul - line + 1);
+ credit_entropy = false;
+ break;
+ }
+
+ char *cmd = strtok(line, " \t\n");
+ if (!cmd)
+ continue;
+
+#ifdef DEBUG
+ fprintf(stderr, "executing command: %s\n", cmd);
+#endif
+
+ if (streq(cmd, "done")) {
+ done = true;
+ continue;
+ }
+
+ if (!run_seed_file_cmd(cmd, salt, seed_file)) {
+ credit_entropy = false;
+ break;
+ }
+ }
+
+ if (credit_entropy && !done) {
+ fputs("missing done command, random seed file probably truncated. disabling entropy credit\n", stderr);
+ credit_entropy = false;
+ }
+
+ int urandom_fd = open("/dev/urandom", O_RDWR, 0);
+ if (urandom_fd == -1) {
+ perror("error opening /dev/urandom");
+ exit(5);
+ }
+
+ if (credit_entropy) {
+ if (ioctl(urandom_fd, RNDADDENTROPY, &rpi) == -1) {
+ perror("ioctl(RNDADDENTROPY)");
+ if (errno == EPERM) {
+ fputs("Continuing without crediting entropy.\n", stderr);
+ }
+ credit_entropy = false;
+ }
+ }
+
+ if (!credit_entropy) {
+ if (write(urandom_fd, &rpi.buf, RAND_POOL_SIZE) != RAND_POOL_SIZE) {
+ fputs("error writing entropy to /dev/urandom\n", stderr);
+ exit(5);
+ }
+ }
+
+ return credit_entropy;
+}
+
+static bool get_rand_pool(unsigned char *buf) {
+ size_t rand_bits_gotten = 0;
+ while (rand_bits_gotten < RAND_POOL_SIZE) {
+ long this_rand_bits_gotten = random_get(buf + rand_bits_gotten, RAND_POOL_SIZE - rand_bits_gotten, 0);
+ if (this_rand_bits_gotten < 0) {
+ switch (errno) {
+ case EAGAIN: continue;
+ case EINTR: return false;
+ default:
+ perror("getrandom");
+ exit(1);
+ }
+ } else {
+ rand_bits_gotten += (size_t)this_rand_bits_gotten;
+ }
+ }
+ return true;
+}
+
+/**
+ * Save entropy to disk.
+ *
+ * \param seed_path the seed file path
+ * \param random_buf the random buffer. if NULL, get our own entropy.
+ * \return true means saved successfully, false means received EINTR
+ */
+static bool save(const char *seed_path, unsigned char *random_buf) {
+ assert(seed_path);
+
+ bool rv = false;
+ char *seed_path_tmp = NULL;
+ char *seed_name_new = NULL;
+
+ unsigned char my_random_buf[RAND_POOL_SIZE];
+ if (!random_buf) {
+ if (!get_rand_pool(my_random_buf))
+ return false;
+ random_buf = my_random_buf;
+ }
+
+ unsigned char machine_id_digest[HASH_LEN];
+ if (!get_machine_id_hash(random_buf, machine_id_digest)) {
+ fputs("cannot obtain machine id, aborting save\n", stderr);
+ return false;
+ }
+ char machine_id_hash[HASH_LEN*2+1];
+ memset(machine_id_hash, 0, HASH_LEN*2);
+ mem2hex(machine_id_hash, machine_id_digest, HASH_LEN);
+ machine_id_hash[sizeof(machine_id_hash)-1] = '\0';
+
+ int seed_dir_fd = -1;
+ int seed_fd = -1;
+ FILE *seed_file = NULL;
+
+ seed_path_tmp = strdup(seed_path);
+
+ // TODO: rewrite when AT_REPLACE gets added... eventually...
+ seed_dir_fd = open(mydirname(seed_path_tmp), O_RDONLY | O_DIRECTORY);
+ if (seed_dir_fd == -1) {
+ perror("error opening seed directory");
+ goto out;
+ }
+
+ unsigned char fs_id_digest[HASH_LEN];
+ if (!get_fs_id_hash(random_buf, fs_id_digest, seed_dir_fd)) {
+ fputs("cannot obtain machine id, aborting save\n", stderr);
+ free(seed_path_tmp);
+ return false;
+ }
+ char fs_id_hash[HASH_LEN*2+1];
+ mem2hex(fs_id_hash, fs_id_digest, HASH_LEN);
+ fs_id_hash[sizeof(fs_id_hash)-1] = '\0';
+
+ strcpy(seed_path_tmp, seed_path);
+ char *seed_name = mybasename(seed_path_tmp);
+ size_t seed_name_new_len = strlen(seed_name) + 6;
+ seed_name_new = malloc(seed_name_new_len);
+ if (!seed_name_new) {
+ fputs("out of memory\n", stderr);
+ free(seed_path_tmp);
+ return false;
+ }
+ assert(seed_name_new_len < INT_MAX);
+ if ((size_t)snprintf(seed_name_new, seed_name_new_len, ".%s.new", seed_name) >= seed_name_new_len)
+ abort();
+
+ seed_fd = openat(seed_dir_fd, seed_name_new, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (seed_fd == -1) {
+ perror("error opening new seed file");
+ goto err;
+ }
+ seed_file = fdopen(seed_fd, "w");
+ if (!seed_file) {
+ perror("error converting seed file fd to stream");
+ goto err;
+ }
+
+ if (fwrite(random_buf, 1, RAND_POOL_SIZE, seed_file) != RAND_POOL_SIZE
+ || fputs(MAGIC, seed_file) == EOF
+ || fputs("machine-id ", seed_file) == EOF
+ || fputs(machine_id_hash, seed_file) == EOF
+ || fputs("\nfs-id ", seed_file) == EOF
+ || fputs(fs_id_hash, seed_file) == EOF
+ || fputs("\ndone\n", seed_file) == EOF) {
+ fputs("error writing new seed file\n", stderr);
+ goto err;
+ }
+
+ if (fflush(seed_file) == EOF) {
+ perror("error flushing new seed file");
+ goto err;
+ }
+ if (fsync(seed_fd) == -1) {
+ perror("error syncing new seed file");
+ goto err;
+ }
+ if (renameat(seed_dir_fd, seed_name_new, seed_dir_fd, seed_name) == -1) {
+ perror("error installing new seed file");
+ goto err;
+ }
+ if (fclose(seed_file) == EOF) {
+ perror("error closing new seed file");
+ goto err;
+ }
+ if (fsync(seed_dir_fd) == -1) {
+ perror("error syncing seed directory");
+ goto out;
+ }
+
+ rv = true;
+ goto out;
+
+err:
+ if (seed_file) {
+ fclose(seed_file);
+ if (unlinkat(seed_dir_fd, seed_name_new, 0) == -1) {
+ perror("error removing temporary seed file");
+ rv = false;
+ }
+ }
+
+out:
+ if (seed_dir_fd != -1) {
+ if (close(seed_dir_fd) == -1) {
+ perror("error closing seed directory");
+ rv = false;
+ }
+ }
+
+ free(seed_path_tmp);
+ free(seed_name_new);
+
+ return rv;
+}
+
+static void sighandler(int signum) {
+ switch (signum) {
+ case SIGHUP:
+ // do nothing; we just needed to interrupt sleep
+ break;
+ case SIGINT:
+ sigint = 1;
+ break;
+ case SIGTERM:
+ sigterm = 1;
+ break;
+ default:
+ abort();
+ }
+}
+
+int main(int argc, char *argv[]) {
+ const char *seed_path = argv[2];
+ FILE *seed_file;
+ unsigned char random_buf[RAND_POOL_SIZE];
+ int exit_status = 0;
+
+ if (argc == 2 && (streq(argv[1], "-h") || streq(argv[1], "--help"))) {
+ usage();
+ exit(0);
+ }
+
+ if (argc != 3) {
+ fprintf(stderr, "invalid argument count, expected 2\n");
+ usage();
+ exit(2);
+ }
+
+ umask(0077);
+
+ if (streq(argv[1], "load")) {
+ bool refresh_seed = true;
+
+ if (streq(seed_path, "-")) {
+ fputs("warning: cannot refresh stdin seed\n", stderr);
+ seed_file = stdin;
+ refresh_seed = false;
+ } else {
+ seed_file = fopen(seed_path, "r");
+ if (!seed_file) {
+ perror("error opening seed file");
+ exit(3);
+ }
+ }
+ if (!load(seed_file))
+ exit_status = 1;
+ if (refresh_seed) {
+ if (random_get(random_buf, sizeof(random_buf), GRND_NONBLOCK) == -1) {
+ if (errno != EAGAIN) {
+ perror("getrandom");
+ exit(1);
+ }
+ daemon(0, 1);
+ close(0);
+ close(1);
+ save(seed_path, NULL);
+ } else {
+ save(seed_path, random_buf);
+ }
+ }
+ exit(exit_status);
+ } else if (streq(argv[1], "save")) {
+ exit(!save(seed_path, NULL));
+ } else if (streq(argv[1], "daemonize")) {
+ if (streq(seed_path, "-")) {
+ fputs("error: seed_path cannot be - for daemonize\n", stderr);
+ exit(2);
+ }
+ seed_file = fopen(seed_path, "r");
+ if (!seed_file) {
+ perror("error opening seed file");
+ exit(3);
+ }
+ load(seed_file);
+
+ struct sigaction sa;
+ sa.sa_handler = sighandler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ daemon(seed_path[0] != '/', 1);
+ close(0);
+ close(1);
+ // don't close stderr because we log there
+ while (true) {
+ if (sigint)
+ exit(exit_status);
+ if (!save(seed_path, NULL)) {
+ exit_status = 1;
+ fputs("an error occurred while saving, trying again later\n", stderr);
+ }
+ if (sigterm)
+ exit(exit_status);
+ sleep(DAEMONIZE_SLEEP_TIME);
+ }
+ } else if (streq(argv[1], "daemonise")) {
+ fputs("invalid mode (did you mean `daemonize'?)\n", stderr);
+ exit(2);
+ } else {
+ fputs("invalid mode, expected load, save, or daemonize\n", stderr);
+ exit(2);
+ }
+}
diff --git a/readall.c b/readall.c
new file mode 100644
index 0000000..9f55697
--- /dev/null
+++ b/readall.c
@@ -0,0 +1,82 @@
+/* by Nominal Animal, 2017: https://stackoverflow.com/a/44894946 */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "readall.h"
+
+/* Size of each input chunk to be
+ read and allocate for. */
+#ifndef READALL_CHUNK
+#define READALL_CHUNK 262144
+#endif
+
+/* This function returns one of the READALL_ constants above.
+ If the return value is zero == READALL_OK, then:
+ (*dataptr) points to a dynamically allocated buffer, with
+ (*sizeptr) chars read from the file.
+ The buffer is allocated for one extra char, which is NUL,
+ and automatically appended after the data.
+ Initial values of (*dataptr) and (*sizeptr) are ignored.
+*/
+int readall(FILE *in, char **dataptr, size_t *sizeptr)
+{
+ char *data = NULL, *temp;
+ size_t size = 0;
+ size_t used = 0;
+ size_t n;
+
+ /* None of the parameters can be NULL. */
+ if (in == NULL || dataptr == NULL || sizeptr == NULL)
+ return READALL_INVALID;
+
+ /* A read error already occurred? */
+ if (ferror(in))
+ return READALL_ERROR;
+
+ while (1) {
+
+ if (used + READALL_CHUNK + 1 > size) {
+ size = used + READALL_CHUNK + 1;
+
+ /* Overflow check. Some ANSI C compilers
+ may optimize this away, though. */
+ if (size <= used) {
+ free(data);
+ return READALL_TOOMUCH;
+ }
+
+ temp = realloc(data, size);
+ if (temp == NULL) {
+ free(data);
+ return READALL_NOMEM;
+ }
+ data = temp;
+ }
+
+ n = fread(data + used, 1, READALL_CHUNK, in);
+ if (n == 0)
+ break;
+
+ used += n;
+ }
+
+ if (ferror(in)) {
+ free(data);
+ return READALL_ERROR;
+ }
+
+ temp = realloc(data, used + 1);
+ if (temp == NULL) {
+ free(data);
+ return READALL_NOMEM;
+ }
+ data = temp;
+ data[used] = '\0';
+
+ *dataptr = data;
+ *sizeptr = used;
+
+ return READALL_OK;
+}
diff --git a/readall.h b/readall.h
new file mode 100644
index 0000000..94c2bfe
--- /dev/null
+++ b/readall.h
@@ -0,0 +1,14 @@
+#ifndef READALL_H
+#define READALL_H
+
+#include <stdio.h>
+
+#define READALL_OK 0 /* Success */
+#define READALL_INVALID -1 /* Invalid parameters */
+#define READALL_ERROR -2 /* Stream error */
+#define READALL_TOOMUCH -3 /* Too much input */
+#define READALL_NOMEM -4 /* Out of memory */
+
+int readall(FILE *in, char **dataptr, size_t *sizeptr);
+
+#endif
diff --git a/sha2.c b/sha2.c
new file mode 100644
index 0000000..4c2b615
--- /dev/null
+++ b/sha2.c
@@ -0,0 +1,204 @@
+/*
+ * FIPS 180-2 SHA-224/256/384/512 implementation
+ * Last update: 02/02/2007
+ * Issue date: 04/30/2005
+ *
+ * Copyright (C) 2013, Con Kolivas <kernel@kolivas.org>
+ * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "sha2.h"
+
+#define UNPACK32(x, str) \
+{ \
+ *((str) + 3) = (uint8_t) ((x) ); \
+ *((str) + 2) = (uint8_t) ((x) >> 8); \
+ *((str) + 1) = (uint8_t) ((x) >> 16); \
+ *((str) + 0) = (uint8_t) ((x) >> 24); \
+}
+
+#define PACK32(str, x) \
+{ \
+ *(x) = ((uint32_t) *((str) + 3) ) \
+ | ((uint32_t) *((str) + 2) << 8) \
+ | ((uint32_t) *((str) + 1) << 16) \
+ | ((uint32_t) *((str) + 0) << 24); \
+}
+
+#define SHA256_SCR(i) \
+{ \
+ w[i] = SHA256_F4(w[i - 2]) + w[i - 7] \
+ + SHA256_F3(w[i - 15]) + w[i - 16]; \
+}
+
+static uint32_t sha256_h0[8] =
+ {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
+
+uint32_t sha256_k[64] =
+ {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
+
+/* SHA-256 functions */
+
+void sha256_transf(sha256_ctx *ctx, const unsigned char *message,
+ unsigned int block_nb)
+{
+ uint32_t w[64];
+ uint32_t wv[8];
+ uint32_t t1, t2;
+ const unsigned char *sub_block;
+ int i;
+
+ int j;
+
+ for (i = 0; i < (int) block_nb; i++) {
+ sub_block = message + (i << 6);
+
+ for (j = 0; j < 16; j++) {
+ PACK32(&sub_block[j << 2], &w[j]);
+ }
+
+ for (j = 16; j < 64; j++) {
+ SHA256_SCR(j);
+ }
+
+ for (j = 0; j < 8; j++) {
+ wv[j] = ctx->h[j];
+ }
+
+ for (j = 0; j < 64; j++) {
+ t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6])
+ + sha256_k[j] + w[j];
+ t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]);
+ wv[7] = wv[6];
+ wv[6] = wv[5];
+ wv[5] = wv[4];
+ wv[4] = wv[3] + t1;
+ wv[3] = wv[2];
+ wv[2] = wv[1];
+ wv[1] = wv[0];
+ wv[0] = t1 + t2;
+ }
+
+ for (j = 0; j < 8; j++) {
+ ctx->h[j] += wv[j];
+ }
+ }
+}
+
+void sha256(const unsigned char *message, unsigned int len, unsigned char *digest)
+{
+ sha256_ctx ctx;
+
+ sha256_init(&ctx);
+ sha256_update(&ctx, message, len);
+ sha256_final(&ctx, digest);
+}
+
+void sha256_init(sha256_ctx *ctx)
+{
+ memcpy(ctx->h, sha256_h0, sizeof(uint32_t)*8);
+ ctx->len = 0;
+ ctx->tot_len = 0;
+}
+
+void sha256_update(sha256_ctx *ctx, const unsigned char *message,
+ unsigned int len)
+{
+ unsigned int block_nb;
+ unsigned int new_len, rem_len, tmp_len;
+ const unsigned char *shifted_message;
+
+ tmp_len = SHA256_BLOCK_SIZE - ctx->len;
+ rem_len = len < tmp_len ? len : tmp_len;
+
+ memcpy(&ctx->block[ctx->len], message, rem_len);
+
+ if (ctx->len + len < SHA256_BLOCK_SIZE) {
+ ctx->len += len;
+ return;
+ }
+
+ new_len = len - rem_len;
+ block_nb = new_len / SHA256_BLOCK_SIZE;
+
+ shifted_message = message + rem_len;
+
+ sha256_transf(ctx, ctx->block, 1);
+ sha256_transf(ctx, shifted_message, block_nb);
+
+ rem_len = new_len % SHA256_BLOCK_SIZE;
+
+ memcpy(ctx->block, &shifted_message[block_nb << 6],
+ rem_len);
+
+ ctx->len = rem_len;
+ ctx->tot_len += (block_nb + 1) << 6;
+}
+
+void sha256_final(sha256_ctx *ctx, unsigned char *digest)
+{
+ unsigned int block_nb;
+ unsigned int pm_len;
+ unsigned int len_b;
+
+ int i;
+
+ block_nb = (1 + ((SHA256_BLOCK_SIZE - 9)
+ < (ctx->len % SHA256_BLOCK_SIZE)));
+
+ len_b = (ctx->tot_len + ctx->len) << 3;
+ pm_len = block_nb << 6;
+
+ memset(ctx->block + ctx->len, 0, pm_len - ctx->len);
+ ctx->block[ctx->len] = 0x80;
+ UNPACK32(len_b, ctx->block + pm_len - 4);
+
+ sha256_transf(ctx, ctx->block, block_nb);
+
+ for (i = 0 ; i < 8; i++) {
+ UNPACK32(ctx->h[i], &digest[i << 2]);
+ }
+}
diff --git a/sha2.h b/sha2.h
new file mode 100644
index 0000000..bd968ba
--- /dev/null
+++ b/sha2.h
@@ -0,0 +1,69 @@
+/*
+ * FIPS 180-2 SHA-224/256/384/512 implementation
+ * Last update: 02/02/2007
+ * Issue date: 04/30/2005
+ *
+ * Copyright (C) 2013, Con Kolivas <kernel@kolivas.org>
+ * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef SHA2_H
+#define SHA2_H
+
+#include <stdint.h>
+
+#define SHA256_DIGEST_SIZE ( 256 / 8)
+#define SHA256_BLOCK_SIZE ( 512 / 8)
+
+#define SHFR(x, n) (x >> n)
+#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n)))
+#define CH(x, y, z) ((x & y) ^ (~x & z))
+#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+
+#define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3))
+#define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10))
+
+typedef struct {
+ unsigned int tot_len;
+ unsigned int len;
+ unsigned char block[2 * SHA256_BLOCK_SIZE];
+ uint32_t h[8];
+} sha256_ctx;
+
+extern uint32_t sha256_k[64];
+
+void sha256_init(sha256_ctx * ctx);
+void sha256_update(sha256_ctx *ctx, const unsigned char *message,
+ unsigned int len);
+void sha256_final(sha256_ctx *ctx, unsigned char *digest);
+void sha256(const unsigned char *message, unsigned int len,
+ unsigned char *digest);
+
+#endif /* !SHA2_H */
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..7dd299a
--- /dev/null
+++ b/util.c
@@ -0,0 +1,93 @@
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+static inline signed char hexchr2int(char c) {
+ /* No, nobody uses EBCDIC anymore. */
+ return (c >= '0' && c <= '9') ? (c - '0') :
+ (c >= 'a' && c <= 'f') ? (c - 'a' + 10) :
+ (c >= 'A' && c <= 'F') ? (c - 'A' + 10) :
+ -1;
+}
+
+/**
+ * Decode hex.
+ *
+ * \param dest where to store the decoded data
+ * \param size the maximum number of bytes to decode
+ * \param src the hex-encoded data
+ *
+ * \return 0 if an error occurred, otherwise the number of bytes written to
+ * dest
+ */
+size_t hex2mem(unsigned char *dest, size_t size, const char *src) {
+#ifdef DEBUG
+ fprintf(stderr, "hex decoding %zu bytes\n", size);
+#endif
+ size_t i;
+ for (i = 0; i < size; i++) {
+ int n1 = hexchr2int(src[2*i]);
+ if (n1 < 0) return 0;
+ int n2 = hexchr2int(src[2*i+1]);
+ if (n2 < 0) return 0;
+ dest[i] = (unsigned char)(n1 << 4 | n2);
+ }
+ return i;
+}
+
+static const char *HEX_CHARS = "0123456789abcdef";
+
+/** Encode hex.
+ *
+ * \param dest where to store the encoded data (must have at least size*2+1 bytes)
+ * \param src the data to encode
+ * \param size the number of bytes to encode
+ */
+void mem2hex(char *dest, const void *src, size_t size) {
+ for (size_t i = 0; i < size; i++) {
+ unsigned char c = *((const unsigned char *)src + i);
+ dest[2*i] = HEX_CHARS[c >> 4];
+ dest[2*i+1] = HEX_CHARS[c & 0xf];
+ }
+ dest[size*2] = '\0';
+}
+
+void hash(const unsigned char salt[static SALT_LEN], unsigned char *out, const void *in, size_t size) {
+ assert(size < INT_MAX - SALT_LEN - 100);
+#ifdef DEBUG
+ fprintf(stderr, "hashing %zu bytes starting with 0x%x ending with 0x%x\n", size, (int)((unsigned char*)in)[0], (int)((unsigned char*)in)[size-1]);
+#endif
+ sha256_ctx ctx;
+ sha256_init(&ctx);
+ //sha256_update(&ctx, salt, SALT_LEN);
+ sha256_update(&ctx, in, (unsigned int)size);
+ sha256_final(&ctx, out);
+}
+
+void print_hash(const unsigned char digest[static HASH_LEN]) {
+#ifdef DEBUG
+ char hash[HASH_LEN*2+1];
+ mem2hex(hash, digest, HASH_LEN);
+ fprintf(stderr, "hash: %s\n", hash);
+#endif
+}
+
+bool hash_match(const unsigned char digest[static HASH_LEN], const char *arg) {
+ unsigned char theirdigest[HASH_LEN];
+ if (hex2mem(theirdigest, sizeof(theirdigest), arg) == 0) {
+ fputs("error decoding hex hash\n", stderr);
+ exit(1);
+ }
+#ifdef DEBUG
+ fprintf(stderr, "comparing hash, theirs: %s = 0x%02x..0x%02x, ours: 0x%02x..0x%02x\n", arg, (int)theirdigest[0], (int)theirdigest[HASH_LEN-1], (int)digest[0], (int)theirdigest[HASH_LEN-1]);
+ fputs(" our ", stderr);
+ print_hash(digest);
+ fputs("their ", stderr);
+ print_hash(theirdigest);
+#endif
+ return !memcmp(digest, theirdigest, HASH_LEN);
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..5c4a604
--- /dev/null
+++ b/util.h
@@ -0,0 +1,41 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "sha2.h"
+
+#define GRND_NONBLOCK 0x01
+#define GRND_RANDOM 0x02
+
+/* The pool size is fixed at 4096 bits since Linux 2.6. */
+#define RAND_POOL_SIZE 512
+/* SHA-256 */
+#define HASH_LEN 32
+/* The salt is the random data */
+#define SALT_LEN RAND_POOL_SIZE
+
+static inline bool streq(const char *s1, const char *s2) {
+ return !strcmp(s1, s2);
+}
+
+static inline ssize_t random_get(void *buf, size_t buflen, unsigned int flags) {
+ long rv = syscall(SYS_getrandom, buf, buflen, flags);
+ if (rv == -1 && errno == ENOSYS) {
+ fputs("getrandom returned ENOSYS. random-seed requires Linux 3.17", stderr);
+ exit(1);
+ }
+ return rv;
+}
+
+size_t hex2mem(unsigned char *dest, size_t size, const char *src);
+void mem2hex(char *dest, const void *src, size_t size);
+void hash(const unsigned char salt[static SALT_LEN], unsigned char *out, const void *in, size_t size);
+bool hash_match(const unsigned char digest[static HASH_LEN], const char *arg);
+
+#endif