Browse Source

Merge upstream version 14

Christoph Biedl 11 months ago
parent
commit
da1d4b8112

+ 9 - 4
.github/workflows/build.yml

@@ -5,26 +5,31 @@ on: [push, pull_request]
 
 
 jobs:
 jobs:
   build:
   build:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-22.04
     continue-on-error: ${{ ! matrix.stable }}
     continue-on-error: ${{ ! matrix.stable }}
     strategy:
     strategy:
       matrix:
       matrix:
         os:
         os:
           - fedora:latest
           - fedora:latest
           - centos:7
           - centos:7
-          - centos:8
+          - quay.io/centos/centos:stream8
+          - quay.io/centos/centos:stream9
           - debian:testing
           - debian:testing
           - debian:latest
           - debian:latest
           - ubuntu:rolling
           - ubuntu:rolling
+          - ubuntu:lunar
+          - ubuntu:jammy
+          - ubuntu:focal
           - ubuntu:bionic
           - ubuntu:bionic
+          - ubuntu:kinetic
         stable: [true]
         stable: [true]
         include:
         include:
-          - os: fedora:rawhide
+          - os: quay.io/fedora/fedora:rawhide
             stable: false
             stable: false
           - os: ubuntu:devel
           - os: ubuntu:devel
             stable: false
             stable: false
     steps:
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
 
       - name: Show OS information
       - name: Show OS information
         run: cat /etc/os-release 2>/dev/null || echo /etc/os-release not available
         run: cat /etc/os-release 2>/dev/null || echo /etc/os-release not available

+ 3 - 3
.github/workflows/coverage.yml

@@ -5,13 +5,13 @@ on: [push, pull_request]
 
 
 jobs:
 jobs:
   build:
   build:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-22.04
     strategy:
     strategy:
       matrix:
       matrix:
         os:
         os:
           - ubuntu:latest
           - ubuntu:latest
     steps:
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
 
       - name: Show OS information
       - name: Show OS information
         run: cat /etc/os-release 2>/dev/null || echo /etc/os-release not available
         run: cat /etc/os-release 2>/dev/null || echo /etc/os-release not available
@@ -47,7 +47,7 @@ jobs:
           [ -z "${ninja}" ] && export ninja=$(command -v ninja-build)
           [ -z "${ninja}" ] && export ninja=$(command -v ninja-build)
           gcovr -r .. -f ../src -f src/ -e ../tests -e tests -x coverage.xml
           gcovr -r .. -f ../src -f src/ -e ../tests -e tests -x coverage.xml
 
 
-      - uses: codecov/codecov-action@v1
+      - uses: codecov/codecov-action@v2
         with:
         with:
           file: build/coverage.xml
           file: build/coverage.xml
           fail_ci_if_error: true # optional (default = false)
           fail_ci_if_error: true # optional (default = false)

+ 14 - 4
.github/workflows/install-dependencies

@@ -6,15 +6,15 @@ debian:*|ubuntu:*)
     apt clean
     apt clean
     apt update
     apt update
     apt -y install gcc meson pkg-config libjose-dev jose libhttp-parser-dev \
     apt -y install gcc meson pkg-config libjose-dev jose libhttp-parser-dev \
-                   systemd gcovr curl socat
+                   systemd gcovr curl socat iproute2
     ;;
     ;;
 
 
-fedora:*)
+*fedora:*)
     echo 'max_parallel_downloads=10' >> /etc/dnf/dnf.conf
     echo 'max_parallel_downloads=10' >> /etc/dnf/dnf.conf
     dnf -y clean all
     dnf -y clean all
     dnf -y --setopt=deltarpm=0 update
     dnf -y --setopt=deltarpm=0 update
     dnf -y install gcc meson pkgconfig libjose-devel jose http-parser-devel \
     dnf -y install gcc meson pkgconfig libjose-devel jose http-parser-devel \
-                   systemd gcovr curl socat
+                   systemd gcovr curl socat iproute
     ;;
     ;;
 
 
 centos:*)
 centos:*)
@@ -23,8 +23,18 @@ centos:*)
     yum install -y yum-utils epel-release
     yum install -y yum-utils epel-release
     yum config-manager -y --set-enabled PowerTools \
     yum config-manager -y --set-enabled PowerTools \
         || yum config-manager -y --set-enabled powertools || :
         || yum config-manager -y --set-enabled powertools || :
-    yum -y install meson socat
+    yum -y install meson socat iproute
     yum-builddep -y tang
     yum-builddep -y tang
     ;;
     ;;
+
+*centos:stream*)
+    dnf -y clean all
+    dnf -y --setopt=deltarpm=0 update
+    dnf install -y dnf-plugins-core epel-release
+    dnf config-manager -y --set-enabled powertools \
+        || dnf config-manager -y --set-enabled crb || :
+    dnf -y install meson socat iproute
+    dnf builddep -y tang --allowerasing --skip-broken --nobest
+    ;;
 esac
 esac
 # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
 # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:

+ 45 - 0
.gitignore

@@ -0,0 +1,45 @@
+*~
+*.a
+*.o
+*.la
+*.lo
+*.log
+*.m4
+*.path
+*.service
+*.so
+*.socket
+*.swp
+*.swo
+*.trs
+.autotools
+.cproject
+.deps
+.dirstamp
+.libs/
+.project
+.settings
+.ycm_extra_conf.py
+.ycm_extra_conf.pyc
+aclocal.m4
+ar-lib
+autom4te.cache
+build
+compile
+config.guess
+config.log
+config.status
+config.sub
+configure
+configure-stamp
+depcomp
+install-sh
+libtool
+ltmain.sh
+Makefile.in
+Makefile
+missing
+tags
+src/tangd
+src/tang
+test-driver

+ 8 - 0
doc/tang.8.adoc

@@ -63,6 +63,14 @@ ifndef::freebsd[]
 link:systemd.unit.5.adoc[*systemd.unit*(5)] and link:systemd.socket.5.adoc[*systemd.socket*(5)] for more information.
 link:systemd.unit.5.adoc[*systemd.unit*(5)] and link:systemd.socket.5.adoc[*systemd.socket*(5)] for more information.
 endif::[]
 endif::[]
 
 
+== STANDALONE OR VIA SYSTEMD
+
+The Tang server can be run via systemd socket activation or standalone
+when the parameter *-l* is passed. The default port used is 9090 and can
+be changed with the *-p* option.
+
+    tang -l -p 9090
+
 == KEY ROTATION
 == KEY ROTATION
 
 
 In order to preserve the security of the system over the long run, you need to
 In order to preserve the security of the system over the long run, you need to

+ 3 - 1
meson.build

@@ -1,5 +1,5 @@
 project('tang', 'c',
 project('tang', 'c',
-  version: '11',
+  version: '14',
   license: 'GPL3+',
   license: 'GPL3+',
   default_options: [
   default_options: [
     'c_std=c99',
     'c_std=c99',
@@ -50,6 +50,8 @@ add_project_arguments(
   language: 'c'
   language: 'c'
 )
 )
 
 
+add_project_arguments('-DVERSION="'+meson.project_version() + '"', language : 'c')
+
 jose = dependency('jose', version: '>=8')
 jose = dependency('jose', version: '>=8')
 a2x = find_program('a2x', required: false)
 a2x = find_program('a2x', required: false)
 compiler = meson.get_compiler('c')
 compiler = meson.get_compiler('c')

+ 8 - 2
src/keys.c

@@ -307,6 +307,9 @@ create_new_keys(const char* jwkdir)
 {
 {
     const char* alg[] = {"ES512", "ECMR", NULL};
     const char* alg[] = {"ES512", "ECMR", NULL};
     char path[PATH_MAX];
     char path[PATH_MAX];
+
+    /* Set default umask for file creation. */
+    umask(0337);
     for (int i = 0; alg[i] != NULL; i++) {
     for (int i = 0; alg[i] != NULL; i++) {
         json_auto_t* jwk = jwk_generate(alg[i]);
         json_auto_t* jwk = jwk_generate(alg[i]);
         if (!jwk) {
         if (!jwk) {
@@ -369,9 +372,12 @@ load_keys(const char* jwkdir)
                 continue;
                 continue;
             }
             }
             filepath[sizeof(filepath) - 1] = '\0';
             filepath[sizeof(filepath) - 1] = '\0';
-            json_auto_t* json = json_load_file(filepath, 0, NULL);
+            json_error_t error;
+            json_auto_t* json = json_load_file(filepath, 0, &error);
             if (!json) {
             if (!json) {
-                fprintf(stderr, "Invalid JSON file (%s); skipping\n", filepath);
+                fprintf(stderr, "Cannot load JSON file (%s); skipping\n", filepath);
+                fprintf(stderr, "error text %s, line %d, col %d, pos %d\n",
+                    error.text, error.line, error.column, error.position);
                 continue;
                 continue;
             }
             }
 
 

+ 1 - 0
src/meson.build

@@ -2,6 +2,7 @@ tangd = executable('tangd',
   'http.c',
   'http.c',
   'keys.c',
   'keys.c',
   'tangd.c',
   'tangd.c',
+  'socket.c',
   dependencies: [jose, http_parser],
   dependencies: [jose, http_parser],
   install: true,
   install: true,
   install_dir: libexecdir
   install_dir: libexecdir

+ 245 - 0
src/socket.c

@@ -0,0 +1,245 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright (c) 2022 Nikos Mavrogiannopoulos
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#include "socket.h"
+
+#define MAX(x,y) ((x)>(y)?(x):(y))
+
+typedef struct socket_list {
+	int s;
+	int family;
+	struct sockaddr addr;
+	struct socket_list *next;
+} socket_list;
+
+static void free_socket_list(socket_list *slist)
+{
+	socket_list *ptr, *oldptr;
+
+	for (ptr = slist; ptr != NULL;) {
+		if (ptr->s >= 0)
+			close(ptr->s);
+		oldptr = ptr;
+		ptr = ptr->next;
+		free(oldptr);
+	}
+}
+
+static int listen_port(socket_list **slist, int port)
+{
+	struct addrinfo hints, *res, *ptr;
+	int y, r, s;
+	char portname[6], strip[64];
+	socket_list *lm;
+
+	snprintf(portname, sizeof(portname), "%d", port);
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_PASSIVE;
+
+	*slist = NULL;
+
+	/* listen to all available (IPv4 and IPv6) address */
+	if ((r = getaddrinfo(NULL, portname, &hints, &res)) != 0) {
+		fprintf(stderr, "getaddrinfo() failed: %s\n", gai_strerror(r));
+		return -1;
+	}
+
+	for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {
+		s = socket(ptr->ai_family, SOCK_STREAM, 0);
+		if (s < 0) {
+			perror("socket() failed");
+			continue;
+		}
+
+		if (ptr->ai_family == AF_INET)
+			fprintf(stderr, "Listening on %s:%d\n", inet_ntop(ptr->ai_family,
+				&((struct sockaddr_in*)ptr->ai_addr)->sin_addr, strip,
+				sizeof(strip)), port);
+		else if (ptr->ai_family == AF_INET6)
+			fprintf(stderr, "Listening on [%s]:%d\n", inet_ntop(ptr->ai_family,
+				&((struct sockaddr_in6*)ptr->ai_addr)->sin6_addr, strip,
+				sizeof(strip)), port);
+
+#if defined(IPV6_V6ONLY)
+		if (ptr->ai_family == AF_INET6) {
+			y = 1;
+			/* avoid listen on ipv6 addresses failing
+			 * because already listening on ipv4 addresses: */
+			if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+				   (const void *) &y, sizeof(y)) < 0) {
+				perror("setsockopt(IPV6_V6ONLY) failed");
+			}
+		}
+#endif
+
+		y = 1;
+		if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+			       (const void *) &y, sizeof(y)) < 0) {
+			perror("setsockopt(SO_REUSEADDR) failed");
+		}
+
+		if (bind(s, ptr->ai_addr, ptr->ai_addrlen) < 0) {
+			perror("bind() failed");
+			close(s);
+			continue;
+		}
+
+		if (listen(s, 1024) < 0) {
+			perror("listen() failed");
+			close(s);
+			r = -1;
+			goto cleanup;
+		}
+
+		lm = calloc(1, sizeof(socket_list));
+		if (lm == NULL) {
+			close(s);
+			r = -1;
+			goto cleanup;
+		}
+		lm->s = s;
+		lm->family = ptr->ai_family;
+		memcpy(&lm->addr, ptr->ai_addr, sizeof(*ptr->ai_addr));
+		lm->next = *slist;
+		*slist = lm;
+	}
+
+	if (*slist == NULL)
+		r = -1;
+	else
+		r = 0;
+
+ cleanup:
+	freeaddrinfo(res);
+	fflush(stderr);
+
+	return r;
+}
+
+static void spawn_process(int fd, const char *jwkdir,
+			  process_request_func pfunc,
+			  socket_list *slist)
+{
+	pid_t pid;
+	socket_list *ptr;
+
+	pid = fork();
+	if (pid == 0) { /* child */
+		for (ptr = slist; ptr != NULL; ptr = ptr->next) {
+			close(ptr->s);
+		}
+		/* Ensure that both stdout and stdin are set */
+		if (dup2(fd, STDOUT_FILENO) < 0) {
+			perror("dup2");
+			close(fd);
+			return;
+		}
+
+		close(fd);
+
+		pfunc(jwkdir, STDOUT_FILENO);
+		free_socket_list(slist);
+		exit(0);
+	} else if (pid == -1) {
+		perror("fork failed");
+	}
+	close(fd);
+}
+
+static void handle_child(int sig)
+{
+	pid_t pid;
+	int status;
+
+	while ((pid = waitpid(-1, &status, WNOHANG)) > 0);
+}
+
+int run_service(const char *jwkdir, int port, process_request_func pfunc)
+{
+	socket_list *slist, *ptr;
+	int r, n = 0, accept_fd;
+	fd_set read_fds;
+	struct timeval tv;
+
+	struct sigaction new_action;
+
+	/* Set up the structure to specify the new action. */
+	new_action.sa_handler = handle_child;
+	sigemptyset (&new_action.sa_mask);
+	new_action.sa_flags = 0;
+	sigaction(SIGCHLD, &new_action, NULL);
+
+	r = listen_port(&slist, port);
+	if (r < 0) {
+		fprintf(stderr, "Could not listen port (%d)\n", port);
+		return -1;
+	}
+
+	while (1) {
+		FD_ZERO(&read_fds);
+		for (ptr = slist; ptr != NULL; ptr = ptr->next) {
+			if (ptr->s > FD_SETSIZE) {
+				fprintf(stderr, "exceeded FD_SETSIZE\n");
+				free_socket_list(slist);
+				return -1;
+			}
+			FD_SET(ptr->s, &read_fds);
+			n = MAX(n, ptr->s);
+		}
+		tv.tv_sec = 1200;
+		tv.tv_usec = 0;
+		n = select(n+1, &read_fds, NULL, NULL, &tv);
+		if (n == -1 && errno == EINTR)
+			continue;
+		if (n < 0) {
+			perror("select");
+			free_socket_list(slist);
+			return -1;
+		}
+
+		for (ptr = slist; ptr != NULL; ptr = ptr->next) {
+			if (FD_ISSET(ptr->s, &read_fds)) {
+				accept_fd = accept(ptr->s, NULL, 0);
+				if (accept_fd < 0) {
+					perror("accept");
+					continue;
+				}
+
+				spawn_process(accept_fd, jwkdir, pfunc, slist);
+			}
+		}
+
+	}
+
+	return 0;
+}

+ 21 - 0
src/socket.h

@@ -0,0 +1,21 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright (c) 2022 Nikos Mavrogiannopoulos
+ *
+ * This program 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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, see <http://www.gnu.org/licenses/>.
+ */
+
+typedef int (*process_request_func)(const char *jwkdir, int in_fileno);
+
+int run_service(const char *jwkdir, int port, process_request_func);

+ 4 - 0
src/tangd-keygen.in

@@ -38,6 +38,10 @@ set_perms() {
 [ $# -eq 3 ] && sig=$2 && exc=$3
 [ $# -eq 3 ] && sig=$2 && exc=$3
 
 
 THP_DEFAULT_HASH=S256     # SHA-256.
 THP_DEFAULT_HASH=S256     # SHA-256.
+
+# Set default umask for file creation.
+umask 0337
+
 jwe=$(jose jwk gen -i '{"alg":"ES512"}')
 jwe=$(jose jwk gen -i '{"alg":"ES512"}')
 [ -z "$sig" ] && sig=$(echo "$jwe" | jose jwk thp -i- -a "${THP_DEFAULT_HASH}")
 [ -z "$sig" ] && sig=$(echo "$jwe" | jose jwk thp -i- -a "${THP_DEFAULT_HASH}")
 echo "$jwe" > "$1/$sig.jwk"
 echo "$jwe" > "$1/$sig.jwk"

+ 4 - 0
src/tangd-rotate-keys.in

@@ -79,6 +79,10 @@ cd "${JWKDIR}" || error "Unable to change to keys directory '${JWKDIR}'"
 
 
     # Create a new set of keys.
     # Create a new set of keys.
     DEFAULT_THP_HASH="S256"
     DEFAULT_THP_HASH="S256"
+
+    # Set default umask for file creation.
+    umask 0337
+
     for alg in "ES512" "ECMR"; do
     for alg in "ES512" "ECMR"; do
         json="$(printf '{"alg": "%s"}' "${alg}")"
         json="$(printf '{"alg": "%s"}' "${alg}")"
         jwe="$(jose jwk gen --input "${json}")"
         jwe="$(jose jwk gen --input "${json}")"

+ 76 - 12
src/tangd.c

@@ -26,9 +26,35 @@
 #include <stdlib.h>
 #include <stdlib.h>
 #include <string.h>
 #include <string.h>
 #include <unistd.h>
 #include <unistd.h>
+#include <getopt.h>
 
 
 #include <jose/jose.h>
 #include <jose/jose.h>
 #include "keys.h"
 #include "keys.h"
+#include "socket.h"
+
+static const struct option long_options[] = {
+	{"port", 1, 0, 'p'},
+	{"listen", 0, 0, 'l'},
+	{"version", 0, 0, 'v'},
+	{"help", 0, 0, 'h'},
+	{NULL, 0, 0, 0}
+};
+
+static void
+print_help(const char *name)
+{
+	fprintf(stderr, "Usage: %s [OPTIONS] <jwkdir>\n", name);
+	fprintf(stderr, "  -p, --port=PORT                 Specify the port to listen (default 9090)\n");
+	fprintf(stderr, "  -l, --listen                    Run as a service and wait for connections\n");
+	fprintf(stderr, "  -v, --version                   Display program version\n");
+	fprintf(stderr, "  -h, --help                      Show this help message\n");
+}
+
+static void
+print_version(void)
+{
+	fprintf(stderr, "tangd %s\n", VERSION);
+}
 
 
 static void
 static void
 str_cleanup(char **str)
 str_cleanup(char **str)
@@ -165,10 +191,12 @@ static struct http_dispatch dispatch[] = {
     {}
     {}
 };
 };
 
 
-int
-main(int argc, char *argv[])
+#define DEFAULT_PORT 9090
+
+static int
+process_request(const char *jwkdir, int in_fileno)
 {
 {
-    struct http_state state = { .dispatch = dispatch, .misc = argv[1] };
+    struct http_state state = { .dispatch = dispatch, .misc = (char*)jwkdir };
     struct http_parser parser = { .data = &state };
     struct http_parser parser = { .data = &state };
     struct stat st = {};
     struct stat st = {};
     char req[4096] = {};
     char req[4096] = {};
@@ -177,23 +205,18 @@ main(int argc, char *argv[])
 
 
     http_parser_init(&parser, HTTP_REQUEST);
     http_parser_init(&parser, HTTP_REQUEST);
 
 
-    if (argc != 2) {
-        fprintf(stderr, "Usage: %s <jwkdir>\n", argv[0]);
-        return EXIT_FAILURE;
-    }
-
-    if (stat(argv[1], &st) != 0) {
-        fprintf(stderr, "Error calling stat() on path: %s: %m\n", argv[1]);
+    if (stat(jwkdir, &st) != 0) {
+        fprintf(stderr, "Error calling stat() on path: %s: %m\n", jwkdir);
         return EXIT_FAILURE;
         return EXIT_FAILURE;
     }
     }
 
 
     if (!S_ISDIR(st.st_mode)) {
     if (!S_ISDIR(st.st_mode)) {
-        fprintf(stderr, "Path is not a directory: %s\n", argv[1]);
+        fprintf(stderr, "Path is not a directory: %s\n", jwkdir);
         return EXIT_FAILURE;
         return EXIT_FAILURE;
     }
     }
 
 
     for (;;) {
     for (;;) {
-        r = read(STDIN_FILENO, &req[rcvd], sizeof(req) - rcvd - 1);
+        r = read(in_fileno, &req[rcvd], sizeof(req) - rcvd - 1);
         if (r == 0)
         if (r == 0)
             return rcvd > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
             return rcvd > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
         if (r < 0)
         if (r < 0)
@@ -214,3 +237,44 @@ main(int argc, char *argv[])
 
 
     return EXIT_SUCCESS;
     return EXIT_SUCCESS;
 }
 }
+
+int
+main(int argc, char *argv[])
+{
+    int listen = 0;
+    int port = DEFAULT_PORT;
+    const char *jwkdir = NULL;
+
+    while (1) {
+	int c = getopt_long(argc, argv, "lp:vh", long_options, NULL);
+	if (c == -1)
+            break;
+
+	switch(c) {
+            case 'v':
+		print_version();
+		return EXIT_SUCCESS;
+	    case 'h':
+		print_help(argv[0]);
+		return EXIT_SUCCESS;
+	    case 'p':
+		port = atoi(optarg);
+		break;
+	    case 'l':
+		listen = 1;
+		break;
+	}
+    }
+
+    if (optind >= argc) {
+        fprintf(stderr, "Usage: %s [OPTION] <jwkdir>\n", argv[0]);
+	return EXIT_FAILURE;
+    }
+    jwkdir = argv[optind++];
+
+    if (listen == 0) { /* process one-shot query from stdin */
+	return process_request(jwkdir, STDIN_FILENO);
+    } else { /* listen and process all incoming connections */
+	return run_service(jwkdir, port, process_request);
+    }
+}

+ 101 - 104
tests/adv

@@ -20,118 +20,115 @@
 
 
 . helpers
 . helpers
 
 
-sanity_check
-
 trap 'on_exit' EXIT
 trap 'on_exit' EXIT
 export TMP=`mktemp -d`
 export TMP=`mktemp -d`
 mkdir -p $TMP/db
 mkdir -p $TMP/db
 
 
-tangd-keygen $TMP/db sig exc
-# Make sure keys generated by tangd-keygen have proper permissions.
-valid_key_perm "${TMP}/db/sig.jwk"
-valid_key_perm "${TMP}/db/exc.jwk"
-
-jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.sig.jwk
-jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.oth.jwk
-
-export PORT=$(random_port)
-start_server "${PORT}"
-export PID=$!
-sleep 0.5
-
-# Make sure requests on the root fail
-fetch / && expected_fail
-
-# The request should fail (404) for non-signature key IDs
-fetch /adv/`jose jwk thp -i $TMP/db/exc.jwk` && expected_fail
-fetch /adv/`jose jwk thp -a S512 -i $TMP/db/exc.jwk` && expected_fail
-
-# The default advertisement fetch should succeed and pass verification
-fetch /adv
-fetch /adv | ver $TMP/db/sig.jwk
-fetch /adv/ | ver $TMP/db/sig.jwk
-
-# Fetching by any thumbprint should work
-fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
-fetch /adv/`jose jwk thp -a S512 -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
-
-# Requesting an adv by an advertised key ID should't be signed by hidden keys
-fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.sig.jwk && expected_fail
-fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
-
-# Verify that the default advertisement is not signed with hidden signature keys
-fetch /adv/ | ver $TMP/db/.oth.jwk && expected_fail
-fetch /adv/ | ver $TMP/db/.sig.jwk && expected_fail
-
-# A private key advertisement is signed by all advertised keys and the requested private key
-fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/sig.jwk
-fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.sig.jwk
-fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
-
-# Verify that the advertisements contain the cty parameter
-fetch /adv | jose fmt -j- -Og protected -SyOg cty -Sq "jwk-set+json" -E
-fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` \
-    | jose fmt -j- -Og signatures -A \
+adv_startup () {
+    tangd-keygen $TMP/db sig exc
+    # Make sure keys generated by tangd-keygen have proper permissions.
+    valid_key_perm "${TMP}/db/sig.jwk"
+    valid_key_perm "${TMP}/db/exc.jwk"
+
+    jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.sig.jwk
+    jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.oth.jwk
+}
+
+adv_second_phase () {
+    # Make sure requests on the root fail
+    fetch / && expected_fail
+
+    # The request should fail (404) for non-signature key IDs
+    fetch /adv/`jose jwk thp -i $TMP/db/exc.jwk` && expected_fail
+    fetch /adv/`jose jwk thp -a S512 -i $TMP/db/exc.jwk` && expected_fail
+
+    # The default advertisement fetch should succeed and pass verification
+    fetch /adv
+    fetch /adv | ver $TMP/db/sig.jwk
+    fetch /adv/ | ver $TMP/db/sig.jwk
+
+    # Fetching by any thumbprint should work
+    fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
+    fetch /adv/`jose jwk thp -a S512 -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
+
+    # Requesting an adv by an advertised key ID should't be signed by hidden keys
+    fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.sig.jwk && expected_fail
+    fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
+
+    # Verify that the default advertisement is not signed with hidden signature keys
+    fetch /adv/ | ver $TMP/db/.oth.jwk && expected_fail
+    fetch /adv/ | ver $TMP/db/.sig.jwk && expected_fail
+
+    # A private key advertisement is signed by all advertised keys and the requested private key
+    fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/sig.jwk
+    fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.sig.jwk
+    fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
+
+    # Verify that the advertisements contain the cty parameter
+    fetch /adv | jose fmt -j- -Og protected -SyOg cty -Sq "jwk-set+json" -E
+    fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` \
+        | jose fmt -j- -Og signatures -A \
                -g 0 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU \
                -g 0 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU \
                -g 1 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU
                -g 1 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU
 
 
-THP_DEFAULT_HASH=S256     # SHA-256.
-test "$(tang-show-keys $PORT)" = "$(jose jwk thp -a "${THP_DEFAULT_HASH}" -i $TMP/db/sig.jwk)"
-
-# Check that new keys will be created if none exist.
-rm -rf "${TMP}/db" && mkdir -p "${TMP}/db"
-fetch /adv
-
-# Now let's make sure the new keys were named using our default thumbprint
-# hash and then rotate them and check if we still create new keys.
-cd "${TMP}/db"
-for k in *.jwk; do
-    # Check for the key name (SHA-256).
-    test "${k}" = "$(jose jwk thp -a "${THP_DEFAULT_HASH}" -i "${k}")".jwk
-    # Rotate the key.
-    mv -f -- "${k}" ".${k}"
-done
-cd -
-fetch /adv
-
-# Lets's now test with multiple pairs of keys.
-for i in 1 2 3 4 5 6 7 8 9; do
-    tangd-keygen "${TMP}"/db other-sig-${i} other-exc-${i}
-    # Make sure the requested keys exist and are valid.
-    validate_sig "${TMP}/db/other-sig-${i}.jwk"
-    validate_exc "${TMP}/db/other-exc-${i}.jwk"
+    THP_DEFAULT_HASH=S256     # SHA-256.
+    test "$(tang-show-keys $PORT)" = "$(jose jwk thp -a "${THP_DEFAULT_HASH}" -i $TMP/db/sig.jwk)"
+
+    # Check that new keys will be created if none exist.
+    rm -rf "${TMP}/db" && mkdir -p "${TMP}/db"
+    fetch /adv
+
+    # Now let's make sure the new keys were named using our default thumbprint
+    # hash and then rotate them and check if we still create new keys.
+    cd "${TMP}/db"
+    for k in *.jwk; do
+        # Check for the key name (SHA-256).
+        test "${k}" = "$(jose jwk thp -a "${THP_DEFAULT_HASH}" -i "${k}")".jwk
+        # Rotate the key.
+        mv -f -- "${k}" ".${k}"
+    done
+    cd -
+    fetch /adv
+
+    # Lets's now test with multiple pairs of keys.
+    for i in 1 2 3 4 5 6 7 8 9; do
+        tangd-keygen "${TMP}"/db other-sig-${i} other-exc-${i}
+        # Make sure the requested keys exist and are valid.
+        validate_sig "${TMP}/db/other-sig-${i}.jwk"
+        validate_exc "${TMP}/db/other-exc-${i}.jwk"
+
+        # Make sure keys generated by tangd-keygen have proper permissions.
+        valid_key_perm "${TMP}/db/other-sig-${i}.jwk"
+        valid_key_perm "${TMP}/db/other-exc-${i}.jwk"
+    done
 
 
-    # Make sure keys generated by tangd-keygen have proper permissions.
-    valid_key_perm "${TMP}/db/other-sig-${i}.jwk"
-    valid_key_perm "${TMP}/db/other-exc-${i}.jwk"
-done
+    # Verify the advertisement is correct.
+    validate "$(fetch /adv)"
+
+    # And make sure we can fetch an adv by its thumbprint.
+    for jwk in "${TMP}"/db/other-sig-*.jwk; do
+	for alg in $(jose alg -k hash); do
+		fetch /adv/"$(jose jwk thp -a "${alg}" -i "${jwk}")" | ver "${jwk}"
+	done
+    done
+
+    # Now let's test keys rotation.
+    tangd-rotate-keys -d "${TMP}/db"
+    for i in 1 2 3 4 5 6 7 8 9; do
+	# Make sure keys were excluded from advertisement.
+	validate_sig "${TMP}/db/.other-sig-${i}.jwk"
+	validate_exc "${TMP}/db/.other-exc-${i}.jwk"
+    done
 
 
-# Verify the advertisement is correct.
-validate "$(fetch /adv)"
+    # And test also that we have valid keys after rotation.
+    thp=
+    for jwk in "${TMP}"/db/*.jwk; do
+	validate_sig "${jwk}" && thp="$(jose jwk thp -a "${THP_DEFAULT_HASH}" \
+										-i "${jwk}")"
 
 
-# And make sure we can fetch an adv by its thumbprint.
-for jwk in "${TMP}"/db/other-sig-*.jwk; do
-    for alg in $(jose alg -k hash); do
-        fetch /adv/"$(jose jwk thp -a "${alg}" -i "${jwk}")" | ver "${jwk}"
+        # Make sure keys generated by tangd-rotate-keys have proper permissions.
+	valid_key_perm "${jwk}"
     done
     done
-done
-
-# Now let's test keys rotation.
-tangd-rotate-keys -d "${TMP}/db"
-for i in 1 2 3 4 5 6 7 8 9; do
-    # Make sure keys were excluded from advertisement.
-    validate_sig "${TMP}/db/.other-sig-${i}.jwk"
-    validate_exc "${TMP}/db/.other-exc-${i}.jwk"
-done
-
-# And test also that we have valid keys after rotation.
-thp=
-for jwk in "${TMP}"/db/*.jwk; do
-    validate_sig "${jwk}" && thp="$(jose jwk thp -a "${THP_DEFAULT_HASH}" \
-                                    -i "${jwk}")"
-
-    # Make sure keys generated by tangd-rotate-keys have proper permissions.
-    valid_key_perm "${jwk}"
-done
-[ -z "${thp}" ] && die "There should be valid keys after rotation"
-test "$(tang-show-keys $PORT)" = "${thp}"
+    [ -z "${thp}" ] && die "There should be valid keys after rotation"
+    test "$(tang-show-keys $PORT)" = "${thp}"
+}

+ 32 - 0
tests/adv-socat

@@ -0,0 +1,32 @@
+#!/bin/sh -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# Author: Nathaniel McCallum <npmccallum@redhat.com>
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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, see <http://www.gnu.org/licenses/>.
+#
+
+. adv
+
+sanity_check
+
+adv_startup
+
+export PORT=$(random_port)
+start_server "${PORT}"
+export PID=$!
+wait_for_port ${PORT}
+
+adv_second_phase

+ 29 - 0
tests/adv-standalone

@@ -0,0 +1,29 @@
+#!/bin/sh -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2022 Nikos Mavrogiannopoulos
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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, see <http://www.gnu.org/licenses/>.
+#
+
+. adv
+
+adv_startup
+
+export PORT=$(random_port)
+start_standalone_server "${PORT}"
+export PID=$!
+wait_for_port ${PORT}
+
+adv_second_phase

+ 32 - 1
tests/helpers

@@ -34,12 +34,43 @@ random_port() {
     fi
     fi
 }
 }
 
 
+check_if_port_listening() {
+    if [ -n "${TANG_BSD}" ]; then
+        sockstat -l|grep "[\:\.]${1}" >/dev/null 2>&1
+    else
+
+        ss -anl|grep "[\:\.]${1}"|grep LISTEN >/dev/null 2>&1
+    fi
+}
+
+wait_for_port()
+{
+    local port="${1}"
+    sleep 1
+
+    local i=0
+    while [ "${i}" -lt 90 ]; do
+        check_if_port_listening "${port}" && return 0
+        i=$((i + 1))
+        echo "try ${i}: waiting for port" >&2
+        sleep 1
+    done
+    return 1
+}
+
 start_server() {
 start_server() {
     "${SOCAT}" TCP-LISTEN:"${1}",bind=127.0.0.1,fork SYSTEM:"${VALGRIND} tangd ${TMP}/db" &
     "${SOCAT}" TCP-LISTEN:"${1}",bind=127.0.0.1,fork SYSTEM:"${VALGRIND} tangd ${TMP}/db" &
 }
 }
 
 
+start_standalone_server() {
+    ${VALGRIND} tangd -p ${1} -l ${TMP}/db &
+}
+
 on_exit() {
 on_exit() {
-    if [ "$PID" ]; then kill "${PID}"; wait "${PID}" || true; fi
+    if [ "${PID}" ]; then
+        kill "${PID}" || true
+        wait "${PID}" || true
+    fi
     [ -d "${TMP}" ] && rm -rf "${TMP}"
     [ -d "${TMP}" ] && rm -rf "${TMP}"
 }
 }
 
 

+ 4 - 2
tests/meson.build

@@ -40,8 +40,10 @@ if socat.found()
   env.set('SOCAT', socat.path())
   env.set('SOCAT', socat.path())
 endif
 endif
 
 
-test('adv', find_program('adv'), env: env, timeout: 60)
-test('rec', find_program('rec'), env: env)
+test('adv-standalone', find_program('adv-standalone'), env: env, timeout: 60)
+test('adv-socat', find_program('adv-socat'), env: env, timeout: 60)
+test('rec-standalone', find_program('rec-standalone'), env: env, timeout: 60)
+test('rec-socat', find_program('rec-socat'), env: env)
 test('test-keys', test_keys, env: env, timeout: 60)
 test('test-keys', test_keys, env: env, timeout: 60)
 
 
 # vim:set ts=2 sw=2 et:
 # vim:set ts=2 sw=2 et:

+ 24 - 28
tests/rec

@@ -20,38 +20,34 @@
 
 
 . helpers
 . helpers
 
 
-sanity_check
-
 trap 'on_exit' EXIT
 trap 'on_exit' EXIT
 export TMP=`mktemp -d`
 export TMP=`mktemp -d`
 mkdir -p $TMP/db
 mkdir -p $TMP/db
 
 
-# Generate the server keys
-tangd-keygen $TMP/db sig exc
-# Make sure keys generated by tangd-keygen have proper permissions.
-valid_key_perm "${TMP}/db/sig.jwk"
-valid_key_perm "${TMP}/db/exc.jwk"
-
-# Generate the client keys
-exc_kid=`jose jwk thp -i $TMP/db/exc.jwk`
-tmp=`jose fmt -j $TMP/db/exc.jwk -Od x -d y -d d -o-`
-jose jwk gen -i "$tmp" -o $TMP/exc.jwk
-jose jwk pub -i $TMP/exc.jwk -o $TMP/exc.pub.jwk
-
-# Start the server
-export PORT=$(random_port)
-start_server "${PORT}"
-export PID=$!
-sleep 0.5
-
-# Make sure that GET fails
-curl -sf http://127.0.0.1:$PORT/rec && expected_fail
-curl -sf http://127.0.0.1:$PORT/rec/ && expected_fail
-
-# Make a recovery request (NOTE: this is insecure! Don't do this in real code!)
-good=`jose jwk exc -i '{"alg":"ECMR","key_ops":["deriveKey"]}' -l $TMP/exc.jwk -r $TMP/db/exc.jwk`
-test=`curl -sf -X POST \
+rec_startup () {
+    # Generate the server keys
+    tangd-keygen $TMP/db sig exc
+    # Make sure keys generated by tangd-keygen have proper permissions.
+    valid_key_perm "${TMP}/db/sig.jwk"
+    valid_key_perm "${TMP}/db/exc.jwk"
+
+    # Generate the client keys
+    exc_kid=`jose jwk thp -i $TMP/db/exc.jwk`
+    tmp=`jose fmt -j $TMP/db/exc.jwk -Od x -d y -d d -o-`
+    jose jwk gen -i "$tmp" -o $TMP/exc.jwk
+    jose jwk pub -i $TMP/exc.jwk -o $TMP/exc.pub.jwk
+}
+
+rec_second_phase () {
+    # Make sure that GET fails
+    curl -sf http://127.0.0.1:$PORT/rec && expected_fail
+    curl -sf http://127.0.0.1:$PORT/rec/ && expected_fail
+
+    # Make a recovery request (NOTE: this is insecure! Don't do this in real code!)
+    good=`jose jwk exc -i '{"alg":"ECMR","key_ops":["deriveKey"]}' -l $TMP/exc.jwk -r $TMP/db/exc.jwk`
+    test=`curl -sf -X POST \
            -H "Content-Type: application/jwk+json" \
            -H "Content-Type: application/jwk+json" \
            --data-binary @- \
            --data-binary @- \
            http://127.0.0.1:$PORT/rec/${exc_kid} < $TMP/exc.pub.jwk`
            http://127.0.0.1:$PORT/rec/${exc_kid} < $TMP/exc.pub.jwk`
-[ "$good" = "$test" ]
+    [ "$good" = "$test" ]
+}

+ 33 - 0
tests/rec-socat

@@ -0,0 +1,33 @@
+#!/bin/sh -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# Author: Nathaniel McCallum <npmccallum@redhat.com>
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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, see <http://www.gnu.org/licenses/>.
+#
+
+. rec
+
+sanity_check
+
+rec_startup
+
+# Start the server
+export PORT=$(random_port)
+start_server "${PORT}"
+export PID=$!
+wait_for_port ${PORT}
+
+rec_second_phase

+ 33 - 0
tests/rec-standalone

@@ -0,0 +1,33 @@
+#!/bin/sh -ex
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2016 Red Hat, Inc.
+# Author: Nathaniel McCallum <npmccallum@redhat.com>
+#
+# This program 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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, see <http://www.gnu.org/licenses/>.
+#
+
+. rec
+
+sanity_check
+
+rec_startup
+
+# Start the server
+export PORT=$(random_port)
+start_standalone_server "${PORT}"
+export PID=$!
+wait_for_port ${PORT}
+
+rec_second_phase

+ 6 - 1
units/meson.build

@@ -3,6 +3,11 @@ tangd_service = configure_file(
   output: 'tangd@.service',
   output: 'tangd@.service',
   configuration: data
   configuration: data
 )
 )
+tangd_socket = configure_file(
+  input: 'tangd.socket.in',
+  output: 'tangd.socket',
+  configuration: data
+)
 if host_machine.system() == 'freebsd'
 if host_machine.system() == 'freebsd'
   tangd_rc = configure_file(
   tangd_rc = configure_file(
     input: 'tangd.rc.in',
     input: 'tangd.rc.in',
@@ -12,7 +17,7 @@ if host_machine.system() == 'freebsd'
     install_mode: ['rwxr-xr-x', 'root', 'wheel']
     install_mode: ['rwxr-xr-x', 'root', 'wheel']
   )
   )
 else
 else
-  units += join_paths(meson.current_source_dir(), 'tangd.socket')
+  units += tangd_socket
   units += tangd_service
   units += tangd_service
 endif
 endif
 
 

+ 3 - 21
units/tangd.rc.in

@@ -4,44 +4,26 @@
 #
 #
 
 
 # Should probably in the future allow running as non-root
 # Should probably in the future allow running as non-root
-# and enable multiple interfaces in some cleaner way.
+# and enable multiple interfaces in some way in the future.
 
 
 # PROVIDE: tangd
 # PROVIDE: tangd
 # REQUIRE: NETWORKING DAEMON
 # REQUIRE: NETWORKING DAEMON
-# KEYWORD: nojail
 
 
 . /etc/rc.subr
 . /etc/rc.subr
 
 
 name="tangd"
 name="tangd"
 desc="Network Presence Binding Daemon (tang)"
 desc="Network Presence Binding Daemon (tang)"
 rcvar="tangd_enable"
 rcvar="tangd_enable"
-command="/usr/local/bin/socat"
 
 
 load_rc_config $name
 load_rc_config $name
 : ${tangd_enable:=no}
 : ${tangd_enable:=no}
-: ${tangd_ip="127.0.0.1"}
 : ${tangd_port="8888"}
 : ${tangd_port="8888"}
 : ${tangd_jwkdir="@jwkdir@"}
 : ${tangd_jwkdir="@jwkdir@"}
 : ${tangd_logfile="/var/log/tang"}
 : ${tangd_logfile="/var/log/tang"}
-: ${tangd_executable="@libexecdir@/tangd"}
 
 
-pidfile="/var/run/${name}.pid"
-required_files="@libexecdir@/${name}"
 required_dirs="${tangd_jwkdir}"
 required_dirs="${tangd_jwkdir}"
 
 
-start_postcmd="${name}_poststart"
-_tangd_listen_args="TCP-LISTEN:${tangd_port},bind=${tangd_ip},fork"
-command_args="${_tangd_listen_args} SYSTEM:\"${tangd_executable} ${tangd_jwkdir} 2>> ${tangd_logfile} \" &"
-
-# Since we may not be the only socat running we can't use the built-in process
-# management, so we'll need to use a pid file and find the pid from unique arguments.
-tangd_poststart() {
-  ps_pid=`ps ax -o pid= -o command= | grep ${_tangd_listen_args} | grep -v grep | awk '{print $1}'`
-  if [ -z "$ps_pid" ]; then
-    err 1 "Cannot get pid for ${command} ${command_args}"
-  fi
-  echo $ps_pid > ${pidfile}
-  return $?
-}
+command="@libexecdir@/${name}"
+command_args="-p ${tangd_port} -l ${tangd_jwkdir} 2>> ${tangd_logfile} &"
 
 
 run_rc_command "$1"
 run_rc_command "$1"

+ 0 - 9
units/tangd.socket

@@ -1,9 +0,0 @@
-[Unit]
-Description=Tang Server socket
-
-[Socket]
-ListenStream=80
-Accept=true
-
-[Install]
-WantedBy=sockets.target

+ 13 - 0
units/tangd.socket.in

@@ -0,0 +1,13 @@
+[Unit]
+Description=Tang Server socket
+Documentation=man:tang(8)
+
+[Socket]
+ListenStream=80
+Accept=true
+
+ExecStartPre=-/usr/bin/chmod --silent 0440 -- @jwkdir@/*.jwk @jwkdir@/.*.jwk
+ExecStartPre=-/usr/bin/chown -R @user@:@group@ @jwkdir@
+
+[Install]
+WantedBy=sockets.target

+ 2 - 0
units/tangd@.service.in

@@ -1,5 +1,6 @@
 [Unit]
 [Unit]
 Description=Tang Server
 Description=Tang Server
+Documentation=man:tang(8)
 
 
 [Service]
 [Service]
 StandardInput=socket
 StandardInput=socket
@@ -7,3 +8,4 @@ StandardOutput=socket
 StandardError=journal
 StandardError=journal
 ExecStart=@libexecdir@/tangd @jwkdir@
 ExecStart=@libexecdir@/tangd @jwkdir@
 User=@user@
 User=@user@
+Group=@group@