aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRick Macklem <rmacklem@FreeBSD.org>2021-02-18 22:08:19 +0000
committerRick Macklem <rmacklem@FreeBSD.org>2021-02-18 22:15:03 +0000
commitb9cbc85d727214cf3e13196ab7e7564e53037f77 (patch)
treeaa326bae98cd6816cd9463b993aad485b854e559
parentc67a2909a629db138227993e1093e66bb6c00af5 (diff)
downloadsrc-b9cbc85d727214cf3e13196ab7e7564e53037f77.tar.gz
src-b9cbc85d727214cf3e13196ab7e7564e53037f77.zip
nfs-over-tls: add user space daemons rpc.tlsclntd and rpc.tlsservd
The kernel changes needed for nfs-over-tls have been committed to main. However, nfs-over-tls requires user space daemons to handle the TLS handshake and other non-application data TLS records. There is one daemon (rpc.tlsclntd) for the client side and one daemon (rpc.tlsservd) for the server side, although they share a fair amount of code found in rpc.tlscommon.c and rpc.tlscommon.h. They use a KTLS enabled OpenSSL to perform the actual work and, as such, are only built when MK_OPENSSL_KTLS is set. Communication with the kernel is done via upcall RPCs done on AF_LOCAL sockets and the custom system call rpctls_syscall. Reviewed by: gbe (man pages only), jhb (usr.sbin/Makefile only) Comments by: jhb MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D28430 Relnotes: yes
-rw-r--r--usr.sbin/Makefile2
-rw-r--r--usr.sbin/rpc.tlsclntd/Makefile29
-rw-r--r--usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8201
-rw-r--r--usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c730
-rw-r--r--usr.sbin/rpc.tlsservd/Makefile29
-rw-r--r--usr.sbin/rpc.tlsservd/rpc.tlscommon.c295
-rw-r--r--usr.sbin/rpc.tlsservd/rpc.tlscommon.h68
-rw-r--r--usr.sbin/rpc.tlsservd/rpc.tlsservd.8348
-rw-r--r--usr.sbin/rpc.tlsservd/rpc.tlsservd.c886
9 files changed, 2588 insertions, 0 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index 39913a327b87..259ab72f2281 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -182,6 +182,8 @@ SUBDIR.${MK_NIS}+= ypserv
SUBDIR.${MK_NIS}+= ypset
SUBDIR.${MK_NTP}+= ntp
SUBDIR.${MK_OPENSSL}+= keyserv
+SUBDIR.${MK_OPENSSL_KTLS}+= rpc.tlsclntd
+SUBDIR.${MK_OPENSSL_KTLS}+= rpc.tlsservd
SUBDIR.${MK_PF}+= ftp-proxy
SUBDIR.${MK_PKGBOOTSTRAP}+= pkg
SUBDIR.${MK_PMC}+= pmc pmcannotate pmccontrol pmcstat pmcstudy
diff --git a/usr.sbin/rpc.tlsclntd/Makefile b/usr.sbin/rpc.tlsclntd/Makefile
new file mode 100644
index 000000000000..1c8481a7889c
--- /dev/null
+++ b/usr.sbin/rpc.tlsclntd/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= rpc.tlsclntd
+MAN= rpc.tlsclntd.8
+SRCS= rpc.tlsclntd.c rpc.tlscommon.c rpctlscd.h rpctlscd_svc.c rpctlscd_xdr.c
+
+CFLAGS+= -I. -I${SRCTOP}/usr.sbin/rpc.tlsservd
+
+LIBADD= ssl crypto util
+
+CLEANFILES= rpctlscd_svc.c rpctlscd_xdr.c rpctlscd.h
+
+RPCSRC= ${SRCTOP}/sys/rpc/rpcsec_tls/rpctlscd.x
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -L -C -M
+
+rpctlscd_svc.c: ${RPCSRC} rpctlscd.h
+ ${RPCGEN} -m -o ${.TARGET} ${RPCSRC}
+
+rpctlscd_xdr.c: ${RPCSRC} rpctlscd.h
+ ${RPCGEN} -c -o ${.TARGET} ${RPCSRC}
+
+rpctlscd.h: ${RPCSRC}
+ ${RPCGEN} -h -o ${.TARGET} ${RPCSRC}
+
+.PATH: ${SRCTOP}/sys/rpc/rpcsec_tls ${SRCTOP}/usr.sbin/rpc.tlsservd
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8 b/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8
new file mode 100644
index 000000000000..23a9d05495c1
--- /dev/null
+++ b/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.8
@@ -0,0 +1,201 @@
+.\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+.\" Authors: Doug Rabson <dfr@rabson.org>
+.\" Developed with Red Inc: Alfred Perlstein <alfred@FreeBSD.org>
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+.\"
+.\" $FreeBSD$
+.\"
+.\" Modified from gssd.8 for rpc.tlsclntd.8 by Rick Macklem.
+.Dd February 17, 2021
+.Dt RPC.TLSCLNTD 8
+.Os
+.Sh NAME
+.Nm rpc.tlsclntd
+.Nd "Sun RPC over TLS Client Daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl C Ar preferred_ciphers
+.Op Fl D Ar certdir
+.Op Fl d
+.Op Fl l Ar CAfile
+.Op Fl m
+.Op Fl p Ar CApath
+.Op Fl r Ar CRLfile
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+program provides support for the client side of the kernel Sun RPC over TLS
+implementation.
+This daemon must be running for the kernel RPC to be able to do a TLS
+connection to a server for an NFS over TLS mount.
+This daemon requires that the kernel be built with
+.Dq options KERNEL_TLS
+and be running on an architecture such as
+.Dq amd64
+that supports a direct map (not i386) with
+.Xr ktls 4
+enabled.
+.Pp
+If either of the
+.Fl l
+or
+.Fl p
+options have been specified, the daemon will require the server's
+certificate to verify
+and have a Fully Qualified Domain Name (FQDN) in it.
+This FQDN must match
+the reverse DNS name for the IP address that
+the server is using for the TCP connection.
+The FQDN may be
+in either the DNS field of the subjectAltName or the CN field of the
+subjectName in the certificate and
+cannot have a wildcard
+.Dq *
+in it.
+.Pp
+If a SIGHUP signal is sent to the daemon it will reload the
+.Dq CRLfile
+and will shut down any extant connections that presented certificates
+during TLS handshake that have been revoked.
+If the
+.Fl r
+option was not specified, the SIGHUP signal will be ignored.
+.Pp
+The daemon will log failed certificate verifications via
+.Xr syslogd 8
+using LOG_INFO | LOG_DAEMON when the
+.Fl l
+or
+.Fl p
+option has been specified.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl C Ar preferred_ciphers , Fl Fl ciphers= Ns Ar preferred_ciphers
+Specify what preferred ciphers are to be used.
+If this option is specified,
+.Dq SSL_CTX_set_cipher_list()
+will be called with
+.Dq preferred_ciphers
+as the argument.
+If this option is not specified, the cipher will be chosen by
+.Xr ssl 7 .
+.It Fl D Ar certdir , Fl Fl certdir= Ns Ar certdir
+Use
+.Dq certdir
+instead of /etc/rpc.tlsclntd for the
+.Fl m
+option.
+.It Fl d , Fl Fl debuglevel
+Run in debug mode.
+In this mode,
+.Nm
+will not fork when it starts.
+.It Fl l Ar CAfile , Fl Fl verifylocs= Ns Ar CAfile
+This specifies the path name of a CAfile which holds the information
+for server certificate verification.
+This path name is used in
+.Dq SSL_CTX_load_verify_locations(ctx,CAfile,NULL)
+and
+.Dq SSL_CTX_set0_CA_list(ctx,SSL_load_client_CA_file(CAfile))
+openssl library calls.
+Note that this is a path name for the file and is not assumed to be
+in
+.Dq certdir .
+.It Fl m , Fl Fl mutualverf
+Enable support for mutual authentication.
+A certificate and associated key must be found in /etc/rpc.tlsclntd
+(or the directory specified by the
+.Fl D
+option)
+in case a server requests a peer certificate.
+The first certificate needs to be in a file named
+.Dq cert.pem
+and the associated key in a file named
+.Dq certkey.pem .
+The
+.Xr mount_nfs 8
+option
+.Fl tlscertname
+can be used to override the default certificate for a given
+NFS mount, where the files use the alternate naming specified by the option.
+If there is a passphrase on the
+.Dq certkey.pem
+file, this daemon will prompt for the passphrase during startup.
+The keys for alternate certificates cannot have passphrases.
+.It Fl p Ar CApath , Fl Fl verifydir= Ns Ar CApath
+This option is similar to the
+.Fl l
+option, but specifies the path of a directory with CA
+certificates in it.
+When this option is used,
+.Dq SSL_CTX_set0_CA_list(ctx,SSL_load_client_CA_file())
+is not called, so a list of CA names is not be passed
+to the server during the TLS handshake.
+The openssl documentation indicates this call is rarely needed.
+.It Fl r Ar CRLfile , Fl Fl crl= Ns Ar CRLfile
+This option specifies a Certificate Revocation List (CRL) file
+that is to be loaded into the verify certificate store and
+checked during verification of the server's certificate.
+This option is meaningless unless either the
+.Fl l
+or
+.Fl p
+have been specified.
+.It Fl v , Fl Fl verbose
+Run in verbose mode.
+In this mode,
+.Nm
+will log activity messages to syslog using LOG_INFO | LOG_DAEMON or to
+stderr, if the
+.Fl d
+option has also been specified.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr openssl 1 ,
+.Xr ktls 4 ,
+.Xr mount_nfs 8 ,
+.Xr rpc.tlsservd 8 ,
+.Xr ssl 7 ,
+.Xr syslogd 8
+.Sh STANDARDS
+The implementation is based on the specification in
+.Rs
+.%B "RFC NNNN"
+.%T "Towards Remote Procedure Call Encryption By Default"
+.Re
+.Sh HISTORY
+The
+.Nm
+manual page first appeared in
+.Fx 13.0 .
+.Sh BUGS
+This daemon cannot be safely shut down and restarted if there are
+any active RPC-over-TLS connections.
+Doing so will orphan the KERNEL_TLS connections, so that they
+can no longer do upcalls successfully, since the
+.Dq SSL *
+structures in userspace have been lost.
diff --git a/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c b/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c
new file mode 100644
index 000000000000..af803f203ffd
--- /dev/null
+++ b/usr.sbin/rpc.tlsclntd/rpc.tlsclntd.c
@@ -0,0 +1,730 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr@rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+/*
+ * Extensively modified from /usr/src/usr.sbin/gssd.c r344402 for
+ * the client side of kernel RPC-over-TLS by Rick Macklem.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/syslog.h>
+#include <sys/time.h>
+#include <err.h>
+#include <getopt.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#include <rpc/rpcsec_tls.h>
+
+#include <openssl/opensslconf.h>
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#include "rpctlscd.h"
+#include "rpc.tlscommon.h"
+
+#ifndef _PATH_RPCTLSCDSOCK
+#define _PATH_RPCTLSCDSOCK "/var/run/rpc.tlsclntd.sock"
+#endif
+#ifndef _PATH_CERTANDKEY
+#define _PATH_CERTANDKEY "/etc/rpc.tlsclntd/"
+#endif
+#ifndef _PATH_RPCTLSCDPID
+#define _PATH_RPCTLSCDPID "/var/run/rpc.tlsclntd.pid"
+#endif
+
+/* Global variables also used by rpc.tlscommon.c. */
+int rpctls_debug_level;
+bool rpctls_verbose;
+SSL_CTX *rpctls_ctx = NULL;
+const char *rpctls_verify_cafile = NULL;
+const char *rpctls_verify_capath = NULL;
+char *rpctls_crlfile = NULL;
+bool rpctls_cert = false;
+bool rpctls_gothup = false;
+struct ssl_list rpctls_ssllist;
+
+static struct pidfh *rpctls_pfh = NULL;
+static const char *rpctls_certdir = _PATH_CERTANDKEY;
+static const char *rpctls_ciphers = NULL;
+static uint64_t rpctls_ssl_refno = 0;
+static uint64_t rpctls_ssl_sec = 0;
+static uint64_t rpctls_ssl_usec = 0;
+
+static void rpctlscd_terminate(int);
+static SSL_CTX *rpctls_setupcl_ssl(void);
+static SSL *rpctls_connect(SSL_CTX *ctx, int s, char *certname,
+ u_int certlen, X509 **certp);
+static void rpctls_huphandler(int sig __unused);
+
+extern void rpctlscd_1(struct svc_req *rqstp, SVCXPRT *transp);
+
+static struct option longopts[] = {
+ { "certdir", required_argument, NULL, 'D' },
+ { "ciphers", required_argument, NULL, 'C' },
+ { "debuglevel", no_argument, NULL, 'd' },
+ { "verifylocs", required_argument, NULL, 'l' },
+ { "mutualverf", no_argument, NULL, 'm' },
+ { "verifydir", required_argument, NULL, 'p' },
+ { "crl", required_argument, NULL, 'r' },
+ { "verbose", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0 }
+};
+
+int
+main(int argc, char **argv)
+{
+ /*
+ * We provide an RPC service on a local-domain socket. The
+ * kernel rpctls code will upcall to this daemon to do the initial
+ * TLS handshake.
+ */
+ struct sockaddr_un sun;
+ int ch, fd, oldmask;
+ SVCXPRT *xprt;
+ bool tls_enable;
+ struct timeval tm;
+ struct timezone tz;
+ pid_t otherpid;
+ size_t tls_enable_len;
+
+ /* Check that another rpctlscd isn't already running. */
+ rpctls_pfh = pidfile_open(_PATH_RPCTLSCDPID, 0600, &otherpid);
+ if (rpctls_pfh == NULL) {
+ if (errno == EEXIST)
+ errx(1, "rpctlscd already running, pid: %d.", otherpid);
+ warn("cannot open or create pidfile");
+ }
+
+ /* Check to see that the ktls is enabled. */
+ tls_enable_len = sizeof(tls_enable);
+ if (sysctlbyname("kern.ipc.tls.enable", &tls_enable, &tls_enable_len,
+ NULL, 0) != 0 || !tls_enable)
+ errx(1, "Kernel TLS not enabled");
+
+ /* Get the time when this daemon is started. */
+ gettimeofday(&tm, &tz);
+ rpctls_ssl_sec = tm.tv_sec;
+ rpctls_ssl_usec = tm.tv_usec;
+
+ rpctls_verbose = false;
+ while ((ch = getopt_long(argc, argv, "CD:dl:mp:r:v", longopts, NULL)) !=
+ -1) {
+ switch (ch) {
+ case 'C':
+ rpctls_ciphers = optarg;
+ break;
+ case 'D':
+ rpctls_certdir = optarg;
+ break;
+ case 'd':
+ rpctls_debug_level++;
+ break;
+ case 'l':
+ rpctls_verify_cafile = optarg;
+ break;
+ case 'm':
+ rpctls_cert = true;
+ break;
+ case 'p':
+ rpctls_verify_capath = optarg;
+ break;
+ case 'r':
+ rpctls_crlfile = optarg;
+ break;
+ case 'v':
+ rpctls_verbose = true;
+ break;
+ default:
+ fprintf(stderr, "usage: %s "
+ "[-C/--ciphers preferred_ciphers] "
+ "[-D/--certdir certdir] [-d/--debuglevel] "
+ "[-l/--verifylocs CAfile] [-m/--mutualverf] "
+ "[-p/--verifydir CApath] [-r/--crl CRLfile] "
+ "[-v/--verbose]\n", argv[0]);
+ exit(1);
+ break;
+ }
+ }
+ if (rpctls_crlfile != NULL && rpctls_verify_cafile == NULL &&
+ rpctls_verify_capath == NULL)
+ errx(1, "-r requires the -l <CAfile> and/or "
+ "-p <CApath> options");
+
+ if (modfind("krpc") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("krpc") < 0 || modfind("krpc") < 0)
+ errx(1, "Kernel RPC is not available");
+ }
+
+ /*
+ * Set up the SSL_CTX *.
+ * Do it now, before daemonizing, in case the private key
+ * is encrypted and requires a passphrase to be entered.
+ */
+ rpctls_ctx = rpctls_setupcl_ssl();
+ if (rpctls_ctx == NULL) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR, "Can't set up TLS context");
+ exit(1);
+ }
+ err(1, "Can't set up TLS context");
+ }
+ LIST_INIT(&rpctls_ssllist);
+
+ if (!rpctls_debug_level) {
+ if (daemon(0, 0) != 0)
+ err(1, "Can't daemonize");
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ }
+ signal(SIGTERM, rpctlscd_terminate);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, rpctls_huphandler);
+
+ pidfile_write(rpctls_pfh);
+
+ memset(&sun, 0, sizeof sun);
+ sun.sun_family = AF_LOCAL;
+ unlink(_PATH_RPCTLSCDSOCK);
+ strcpy(sun.sun_path, _PATH_RPCTLSCDSOCK);
+ sun.sun_len = SUN_LEN(&sun);
+ fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR, "Can't create local rpctlscd socket");
+ exit(1);
+ }
+ err(1, "Can't create local rpctlscd socket");
+ }
+ oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+ if (bind(fd, (struct sockaddr *)&sun, sun.sun_len) < 0) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR, "Can't bind local rpctlscd socket");
+ exit(1);
+ }
+ err(1, "Can't bind local rpctlscd socket");
+ }
+ umask(oldmask);
+ if (listen(fd, SOMAXCONN) < 0) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't listen on local rpctlscd socket");
+ exit(1);
+ }
+ err(1, "Can't listen on local rpctlscd socket");
+ }
+ xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+ if (!xprt) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't create transport for local rpctlscd socket");
+ exit(1);
+ }
+ err(1, "Can't create transport for local rpctlscd socket");
+ }
+ if (!svc_reg(xprt, RPCTLSCD, RPCTLSCDVERS, rpctlscd_1, NULL)) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't register service for local rpctlscd socket");
+ exit(1);
+ }
+ err(1, "Can't register service for local rpctlscd socket");
+ }
+
+ rpctls_syscall(RPCTLS_SYSC_CLSETPATH, _PATH_RPCTLSCDSOCK);
+
+ rpctls_svc_run();
+
+ rpctls_syscall(RPCTLS_SYSC_CLSHUTDOWN, "");
+
+ SSL_CTX_free(rpctls_ctx);
+ EVP_cleanup();
+ return (0);
+}
+
+bool_t
+rpctlscd_null_1_svc(__unused void *argp, __unused void *result,
+ __unused struct svc_req *rqstp)
+{
+
+ rpctls_verbose_out("rpctlscd_null: done\n");
+ return (TRUE);
+}
+
+bool_t
+rpctlscd_connect_1_svc(struct rpctlscd_connect_arg *argp,
+ struct rpctlscd_connect_res *result, __unused struct svc_req *rqstp)
+{
+ int s;
+ SSL *ssl;
+ struct ssl_entry *newslp;
+ X509 *cert;
+
+ rpctls_verbose_out("rpctlsd_connect: started\n");
+ /* Get the socket fd from the kernel. */
+ s = rpctls_syscall(RPCTLS_SYSC_CLSOCKET, "");
+ if (s < 0) {
+ result->reterr = RPCTLSERR_NOSOCKET;
+ return (TRUE);
+ }
+
+ /* Do a TLS connect handshake. */
+ ssl = rpctls_connect(rpctls_ctx, s, argp->certname.certname_val,
+ argp->certname.certname_len, &cert);
+ if (ssl == NULL) {
+ rpctls_verbose_out("rpctlsd_connect: can't do TLS "
+ "handshake\n");
+ result->reterr = RPCTLSERR_NOSSL;
+ } else {
+ result->reterr = RPCTLSERR_OK;
+ result->sec = rpctls_ssl_sec;
+ result->usec = rpctls_ssl_usec;
+ result->ssl = ++rpctls_ssl_refno;
+ /* Hard to believe this will ever wrap around.. */
+ if (rpctls_ssl_refno == 0)
+ result->ssl = ++rpctls_ssl_refno;
+ }
+
+ if (ssl == NULL) {
+ /*
+ * For RPC-over-TLS, this upcall is expected
+ * to close off the socket.
+ */
+ close(s);
+ return (TRUE);
+ }
+
+ /* Maintain list of all current SSL *'s */
+ newslp = malloc(sizeof(*newslp));
+ newslp->refno = rpctls_ssl_refno;
+ newslp->s = s;
+ newslp->shutoff = false;
+ newslp->ssl = ssl;
+ newslp->cert = cert;
+ LIST_INSERT_HEAD(&rpctls_ssllist, newslp, next);
+ return (TRUE);
+}
+
+bool_t
+rpctlscd_handlerecord_1_svc(struct rpctlscd_handlerecord_arg *argp,
+ struct rpctlscd_handlerecord_res *result, __unused struct svc_req *rqstp)
+{
+ struct ssl_entry *slp;
+ int ret;
+ char junk;
+
+ slp = NULL;
+ if (argp->sec == rpctls_ssl_sec && argp->usec ==
+ rpctls_ssl_usec) {
+ LIST_FOREACH(slp, &rpctls_ssllist, next) {
+ if (slp->refno == argp->ssl)
+ break;
+ }
+ }
+
+ if (slp != NULL) {
+ rpctls_verbose_out("rpctlscd_handlerecord fd=%d\n",
+ slp->s);
+ /*
+ * An SSL_read() of 0 bytes should fail, but it should
+ * handle the non-application data record before doing so.
+ */
+ ret = SSL_read(slp->ssl, &junk, 0);
+ if (ret <= 0) {
+ /* Check to see if this was a close alert. */
+ ret = SSL_get_shutdown(slp->ssl);
+ if ((ret & (SSL_SENT_SHUTDOWN |
+ SSL_RECEIVED_SHUTDOWN)) == SSL_RECEIVED_SHUTDOWN)
+ SSL_shutdown(slp->ssl);
+ } else {
+ if (rpctls_debug_level == 0)
+ syslog(LOG_ERR, "SSL_read returned %d", ret);
+ else
+ fprintf(stderr, "SSL_read returned %d\n", ret);
+ }
+ result->reterr = RPCTLSERR_OK;
+ } else
+ result->reterr = RPCTLSERR_NOSSL;
+ return (TRUE);
+}
+
+bool_t
+rpctlscd_disconnect_1_svc(struct rpctlscd_disconnect_arg *argp,
+ struct rpctlscd_disconnect_res *result, __unused struct svc_req *rqstp)
+{
+ struct ssl_entry *slp;
+ int ret;
+
+ slp = NULL;
+ if (argp->sec == rpctls_ssl_sec && argp->usec ==
+ rpctls_ssl_usec) {
+ LIST_FOREACH(slp, &rpctls_ssllist, next) {
+ if (slp->refno == argp->ssl)
+ break;
+ }
+ }
+
+ if (slp != NULL) {
+ rpctls_verbose_out("rpctlscd_disconnect: fd=%d closed\n",
+ slp->s);
+ LIST_REMOVE(slp, next);
+ if (!slp->shutoff) {
+ ret = SSL_get_shutdown(slp->ssl);
+ /*
+ * Do an SSL_shutdown() unless a close alert has
+ * already been sent.
+ */
+ if ((ret & SSL_SENT_SHUTDOWN) == 0)
+ SSL_shutdown(slp->ssl);
+ }
+ SSL_free(slp->ssl);
+ if (slp->cert != NULL)
+ X509_free(slp->cert);
+ /*
+ * For RPC-over-TLS, this upcall is expected
+ * to close off the socket.
+ */
+ if (!slp->shutoff)
+ shutdown(slp->s, SHUT_WR);
+ close(slp->s);
+ free(slp);
+ result->reterr = RPCTLSERR_OK;
+ } else
+ result->reterr = RPCTLSERR_NOCLOSE;
+ return (TRUE);
+}
+
+int
+rpctlscd_1_freeresult(__unused SVCXPRT *transp, __unused xdrproc_t xdr_result,
+ __unused caddr_t result)
+{
+
+ return (TRUE);
+}
+
+static void
+rpctlscd_terminate(int sig __unused)
+{
+
+ rpctls_syscall(RPCTLS_SYSC_CLSHUTDOWN, "");
+ pidfile_remove(rpctls_pfh);
+ exit(0);
+}
+
+static SSL_CTX *
+rpctls_setupcl_ssl(void)
+{
+ SSL_CTX *ctx;
+ long flags;
+ char path[PATH_MAX];
+ size_t len, rlen;
+ int ret;
+
+ SSL_library_init();
+ SSL_load_error_strings();
+ OpenSSL_add_all_algorithms();
+
+ ctx = SSL_CTX_new(TLS_client_method());
+ if (ctx == NULL) {
+ rpctls_verbose_out("rpctls_setupcl_ssl: SSL_CTX_new "
+ "failed\n");
+ return (NULL);
+ }
+ SSL_CTX_set_ecdh_auto(ctx, 1);
+
+ if (rpctls_ciphers != NULL) {
+ /*
+ * Set preferred ciphers, since KERN_TLS only supports a
+ * few of them.
+ */
+ ret = SSL_CTX_set_cipher_list(ctx, rpctls_ciphers);
+ if (ret == 0) {
+ rpctls_verbose_out("rpctls_setupcl_ssl: "
+ "SSL_CTX_set_cipher_list failed: %s\n",
+ rpctls_ciphers);
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ }
+
+ /*
+ * If rpctls_cert is true, a certificate and key exists in
+ * rpctls_certdir, so that it can do mutual authentication.
+ */
+ if (rpctls_cert) {
+ /* Get the cert.pem and certkey.pem files. */
+ len = strlcpy(path, rpctls_certdir, sizeof(path));
+ rlen = sizeof(path) - len;
+ if (strlcpy(&path[len], "cert.pem", rlen) != 8) {
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ ret = SSL_CTX_use_certificate_file(ctx, path,
+ SSL_FILETYPE_PEM);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_setupcl_ssl: can't use "
+ "certificate file path=%s ret=%d\n", path, ret);
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ if (strlcpy(&path[len], "certkey.pem", rlen) != 11) {
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ ret = SSL_CTX_use_PrivateKey_file(ctx, path,
+ SSL_FILETYPE_PEM);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_setupcl_ssl: Can't use "
+ "private key path=%s ret=%d\n", path, ret);
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ }
+
+ if (rpctls_verify_cafile != NULL || rpctls_verify_capath != NULL) {
+ if (rpctls_crlfile != NULL) {
+ ret = rpctls_loadcrlfile(ctx);
+ if (ret == 0) {
+ rpctls_verbose_out("rpctls_setupcl_ssl: "
+ "Load CRLfile failed\n");
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ }
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+ ret = 1;
+ if (rpctls_verify_cafile != NULL)
+ ret = SSL_CTX_load_verify_file(ctx,
+ rpctls_verify_cafile);
+ if (ret != 0 && rpctls_verify_capath != NULL)
+ ret = SSL_CTX_load_verify_dir(ctx,
+ rpctls_verify_capath);
+#else
+ ret = SSL_CTX_load_verify_locations(ctx,
+ rpctls_verify_cafile, rpctls_verify_capath);
+#endif
+ if (ret == 0) {
+ rpctls_verbose_out("rpctls_setupcl_ssl: "
+ "Can't load verify locations\n");
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ /*
+ * The man page says that the
+ * SSL_CTX_set0_CA_list() call is not normally
+ * needed, but I believe it is harmless.
+ */
+ if (rpctls_verify_cafile != NULL)
+ SSL_CTX_set0_CA_list(ctx,
+ SSL_load_client_CA_file(rpctls_verify_cafile));
+ }
+
+ /* RPC-over-TLS must use TLSv1.3, according to the IETF draft.*/
+#ifdef notyet
+ flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 |
+ SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2;
+#else
+ flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1_3;
+#endif
+ SSL_CTX_set_options(ctx, flags);
+ SSL_CTX_clear_mode(ctx, SSL_MODE_NO_KTLS_TX | SSL_MODE_NO_KTLS_RX);
+ return (ctx);
+}
+
+static SSL *
+rpctls_connect(SSL_CTX *ctx, int s, char *certname, u_int certlen, X509 **certp)
+{
+ SSL *ssl;
+ X509 *cert;
+ struct sockaddr_storage ad;
+ struct sockaddr *sad;
+ char hostnam[NI_MAXHOST], path[PATH_MAX];
+ int gethostret, ret;
+ char *cp, *cp2;
+ size_t len, rlen;
+ long verfret;
+
+ *certp = NULL;
+ sad = (struct sockaddr *)&ad;
+ ssl = SSL_new(ctx);
+ if (ssl == NULL) {
+ rpctls_verbose_out("rpctls_connect: "
+ "SSL_new failed\n");
+ return (NULL);
+ }
+ if (SSL_set_fd(ssl, s) != 1) {
+ rpctls_verbose_out("rpctls_connect: "
+ "SSL_set_fd failed\n");
+ SSL_free(ssl);
+ return (NULL);
+ }
+
+ /*
+ * If rpctls_cert is true and certname is set, a alternate certificate
+ * and key exists in files named <certname>.pem and <certname>key.pem
+ * in rpctls_certdir that is to be used for mutual authentication.
+ */
+ if (rpctls_cert && certlen > 0) {
+ len = strlcpy(path, rpctls_certdir, sizeof(path));
+ rlen = sizeof(path) - len;
+ if (rlen <= certlen) {
+ SSL_free(ssl);
+ return (NULL);
+ }
+ memcpy(&path[len], certname, certlen);
+ rlen -= certlen;
+ len += certlen;
+ path[len] = '\0';
+ if (strlcpy(&path[len], ".pem", rlen) != 4) {
+ SSL_free(ssl);
+ return (NULL);
+ }
+ ret = SSL_use_certificate_file(ssl, path, SSL_FILETYPE_PEM);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_connect: can't use "
+ "certificate file path=%s ret=%d\n", path, ret);
+ SSL_free(ssl);
+ return (NULL);
+ }
+ if (strlcpy(&path[len], "key.pem", rlen) != 7) {
+ SSL_free(ssl);
+ return (NULL);
+ }
+ ret = SSL_use_PrivateKey_file(ssl, path, SSL_FILETYPE_PEM);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_connect: Can't use "
+ "private key path=%s ret=%d\n", path, ret);
+ SSL_free(ssl);
+ return (NULL);
+ }
+ }
+
+ ret = SSL_connect(ssl);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_connect: "
+ "SSL_connect failed %d\n",
+ ret);
+ SSL_free(ssl);
+ return (NULL);
+ }
+
+ cert = SSL_get_peer_certificate(ssl);
+ if (cert == NULL) {
+ rpctls_verbose_out("rpctls_connect: get peer"
+ " certificate failed\n");
+ SSL_free(ssl);
+ return (NULL);
+ }
+ gethostret = rpctls_gethost(s, sad, hostnam, sizeof(hostnam));
+ if (gethostret == 0)
+ hostnam[0] = '\0';
+ verfret = SSL_get_verify_result(ssl);
+ if (verfret == X509_V_OK && (rpctls_verify_cafile != NULL ||
+ rpctls_verify_capath != NULL) && (gethostret == 0 ||
+ rpctls_checkhost(sad, cert, X509_CHECK_FLAG_NO_WILDCARDS) != 1))
+ verfret = X509_V_ERR_HOSTNAME_MISMATCH;
+ if (verfret != X509_V_OK && (rpctls_verify_cafile != NULL ||
+ rpctls_verify_capath != NULL)) {
+ if (verfret != X509_V_OK) {
+ cp = X509_NAME_oneline(X509_get_issuer_name(cert),
+ NULL, 0);
+ cp2 = X509_NAME_oneline(X509_get_subject_name(cert),
+ NULL, 0);
+ if (rpctls_debug_level == 0)
+ syslog(LOG_INFO | LOG_DAEMON,
+ "rpctls_connect: client IP %s "
+ "issuerName=%s subjectName=%s verify "
+ "failed %s\n", hostnam, cp, cp2,
+ X509_verify_cert_error_string(verfret));
+ else
+ fprintf(stderr,
+ "rpctls_connect: client IP %s "
+ "issuerName=%s subjectName=%s verify "
+ "failed %s\n", hostnam, cp, cp2,
+ X509_verify_cert_error_string(verfret));
+ }
+ X509_free(cert);
+ SSL_free(ssl);
+ return (NULL);
+ }
+
+ /* Check to see if ktls is enabled on the connection. */
+ ret = BIO_get_ktls_send(SSL_get_wbio(ssl));
+ rpctls_verbose_out("rpctls_connect: BIO_get_ktls_send=%d\n", ret);
+ if (ret != 0) {
+ ret = BIO_get_ktls_recv(SSL_get_rbio(ssl));
+ rpctls_verbose_out("rpctls_connect: BIO_get_ktls_recv=%d\n",
+ ret);
+ }
+ if (ret == 0) {
+ if (rpctls_debug_level == 0)
+ syslog(LOG_ERR, "ktls not working\n");
+ else
+ fprintf(stderr, "ktls not working\n");
+ X509_free(cert);
+ SSL_free(ssl);
+ return (NULL);
+ }
+ if (ret == X509_V_OK && (rpctls_verify_cafile != NULL ||
+ rpctls_verify_capath != NULL) && rpctls_crlfile != NULL)
+ *certp = cert;
+ else
+ X509_free(cert);
+
+ return (ssl);
+}
+
+static void
+rpctls_huphandler(int sig __unused)
+{
+
+ rpctls_gothup = true;
+}
diff --git a/usr.sbin/rpc.tlsservd/Makefile b/usr.sbin/rpc.tlsservd/Makefile
new file mode 100644
index 000000000000..4424c38a8502
--- /dev/null
+++ b/usr.sbin/rpc.tlsservd/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= rpc.tlsservd
+MAN= rpc.tlsservd.8
+SRCS= rpc.tlsservd.c rpc.tlscommon.c rpctlssd.h rpctlssd_svc.c rpctlssd_xdr.c
+
+CFLAGS+= -I.
+
+LIBADD= ssl crypto util
+
+CLEANFILES= rpctlssd_svc.c rpctlssd_xdr.c rpctlssd.h
+
+RPCSRC= ${SRCTOP}/sys/rpc/rpcsec_tls/rpctlssd.x
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -L -C -M
+
+rpctlssd_svc.c: ${RPCSRC} rpctlssd.h
+ ${RPCGEN} -m -o ${.TARGET} ${RPCSRC}
+
+rpctlssd_xdr.c: ${RPCSRC} rpctlssd.h
+ ${RPCGEN} -c -o ${.TARGET} ${RPCSRC}
+
+rpctlssd.h: ${RPCSRC}
+ ${RPCGEN} -h -o ${.TARGET} ${RPCSRC}
+
+.PATH: ${SRCTOP}/sys/rpc/rpcsec_tls
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.tlsservd/rpc.tlscommon.c b/usr.sbin/rpc.tlsservd/rpc.tlscommon.c
new file mode 100644
index 000000000000..f5b88ffd0166
--- /dev/null
+++ b/usr.sbin/rpc.tlsservd/rpc.tlscommon.c
@@ -0,0 +1,295 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Rick Macklem
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/queue.h>
+#include <sys/syslog.h>
+#include <sys/select.h>
+#include <sys/time.h>
+
+#include <netdb.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <rpc/rpc.h>
+
+#include <openssl/opensslconf.h>
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#include "rpc.tlscommon.h"
+
+/*
+ * How long to delay a reload of the CRL when there are RPC request(s)
+ * to process, in usec. Must be less than 1second.
+ */
+#define RELOADDELAY 250000
+
+void
+rpctls_svc_run(void)
+{
+ int ret;
+ struct timeval tv;
+ fd_set readfds;
+ uint64_t curtime, nexttime;
+ struct timespec tp;
+ sigset_t sighup_mask;
+
+ /* Expand svc_run() here so that we can call rpctls_loadcrlfile(). */
+ curtime = nexttime = 0;
+ sigemptyset(&sighup_mask);
+ sigaddset(&sighup_mask, SIGHUP);
+ for (;;) {
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+ curtime = tp.tv_sec;
+ curtime = curtime * 1000000 + tp.tv_nsec / 1000;
+ sigprocmask(SIG_BLOCK, &sighup_mask, NULL);
+ if (rpctls_gothup && curtime >= nexttime) {
+ rpctls_gothup = false;
+ sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
+ ret = rpctls_loadcrlfile(rpctls_ctx);
+ if (ret != 0)
+ rpctls_checkcrl();
+ else
+ rpctls_verbose_out("rpc.tlsservd: Can't "
+ "reload CRLfile\n");
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+ nexttime = tp.tv_sec;
+ nexttime = nexttime * 1000000 + tp.tv_nsec / 1000 +
+ RELOADDELAY;
+ } else
+ sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
+
+ /*
+ * If a reload is pending, poll for received request(s),
+ * otherwise set a RELOADDELAY timeout, since a SIGHUP
+ * could be processed between the got_sighup test and
+ * the select() system call.
+ */
+ tv.tv_sec = 0;
+ if (rpctls_gothup)
+ tv.tv_usec = 0;
+ else
+ tv.tv_usec = RELOADDELAY;
+ readfds = svc_fdset;
+ switch (select(svc_maxfd + 1, &readfds, NULL, NULL, &tv)) {
+ case -1:
+ if (errno == EINTR) {
+ /* Allow a reload now. */
+ nexttime = 0;
+ continue;
+ }
+ syslog(LOG_ERR, "rpc.tls daemon died: select: %m");
+ exit(1);
+ case 0:
+ /* Allow a reload now. */
+ nexttime = 0;
+ continue;
+ default:
+ svc_getreqset(&readfds);
+ }
+ }
+}
+
+/*
+ * (re)load the CRLfile into the certificate verification store.
+ */
+int
+rpctls_loadcrlfile(SSL_CTX *ctx)
+{
+ X509_STORE *certstore;
+ X509_LOOKUP *certlookup;
+ int ret;
+
+ if ((rpctls_verify_cafile != NULL ||
+ rpctls_verify_capath != NULL) &&
+ rpctls_crlfile != NULL) {
+ certstore = SSL_CTX_get_cert_store(ctx);
+ certlookup = X509_STORE_add_lookup(
+ certstore, X509_LOOKUP_file());
+ ret = 0;
+ if (certlookup != NULL)
+ ret = X509_load_crl_file(certlookup,
+ rpctls_crlfile, X509_FILETYPE_PEM);
+ if (ret != 0)
+ ret = X509_STORE_set_flags(certstore,
+ X509_V_FLAG_CRL_CHECK |
+ X509_V_FLAG_CRL_CHECK_ALL);
+ if (ret == 0) {
+ rpctls_verbose_out(
+ "rpctls_loadcrlfile: Can't"
+ " load CRLfile=%s\n",
+ rpctls_crlfile);
+ return (ret);
+ }
+ }
+ return (1);
+}
+
+/*
+ * Read the CRL file and check for any extant connections
+ * that might now be revoked.
+ */
+void
+rpctls_checkcrl(void)
+{
+ struct ssl_entry *slp;
+ BIO *infile;
+ X509_CRL *crl;
+ X509_REVOKED *revoked;
+ char *cp, *cp2, nullstr[1];
+ int ret;
+
+ if (rpctls_crlfile == NULL || (rpctls_verify_cafile == NULL &&
+ rpctls_verify_capath == NULL))
+ return;
+ infile = BIO_new(BIO_s_file());
+ if (infile == NULL) {
+ rpctls_verbose_out("rpctls_checkcrl: Cannot BIO_new\n");
+ return;
+ }
+ ret = BIO_read_filename(infile, rpctls_crlfile);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_checkcrl: Cannot read CRL file\n");
+ BIO_free(infile);
+ return;
+ }
+
+ nullstr[0] = '\0';
+ for (crl = PEM_read_bio_X509_CRL(infile, NULL, NULL, nullstr);
+ crl != NULL; crl = PEM_read_bio_X509_CRL(infile, NULL, NULL,
+ nullstr)) {
+ LIST_FOREACH(slp, &rpctls_ssllist, next) {
+ if (slp->cert != NULL) {
+ ret = X509_CRL_get0_by_cert(crl, &revoked,
+ slp->cert);
+ /*
+ * Do a shutdown on the socket, so that it
+ * can no longer be used. The kernel RPC
+ * code will notice the socket is disabled
+ * and will do a disconnect upcall, which will
+ * close the socket.
+ */
+ if (ret == 1) {
+ cp2 = X509_NAME_oneline(
+ X509_get_subject_name(slp->cert),
+ NULL, 0);
+ cp = X509_NAME_oneline(
+ X509_get_issuer_name(slp->cert),
+ NULL, 0);
+ if (rpctls_debug_level == 0)
+ syslog(LOG_INFO | LOG_DAEMON,
+ "rpctls_daemon: Certificate"
+ " Revoked "
+ "issuerName=%s "
+ "subjectName=%s: "
+ "TCP connection closed",
+ cp, cp2);
+ else
+ fprintf(stderr,
+ "rpctls_daemon: Certificate"
+ " Revoked "
+ "issuerName=%s "
+ "subjectName=%s: "
+ "TCP connection closed",
+ cp, cp2);
+ shutdown(slp->s, SHUT_WR);
+ slp->shutoff = true;
+ }
+ }
+ }
+ X509_CRL_free(crl);
+ }
+ BIO_free(infile);
+}
+
+void
+rpctls_verbose_out(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (rpctls_verbose) {
+ va_start(ap, fmt);
+ if (rpctls_debug_level == 0)
+ vsyslog(LOG_INFO | LOG_DAEMON, fmt, ap);
+ else
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+}
+
+/*
+ * Check a IP address against any host address in the
+ * certificate. Basically getnameinfo(3) and
+ * X509_check_host().
+ */
+int
+rpctls_checkhost(struct sockaddr *sad, X509 *cert, unsigned int wildcard)
+{
+ char hostnam[NI_MAXHOST];
+ int ret;
+
+ if (getnameinfo((const struct sockaddr *)sad,
+ sad->sa_len, hostnam, sizeof(hostnam),
+ NULL, 0, NI_NAMEREQD) != 0)
+ return (0);
+ rpctls_verbose_out("rpctls_checkhost: DNS %s\n",
+ hostnam);
+ ret = X509_check_host(cert, hostnam, strlen(hostnam),
+ wildcard, NULL);
+ return (ret);
+}
+
+/*
+ * Get the peer's IP address.
+ */
+int
+rpctls_gethost(int s, struct sockaddr *sad, char *hostip, size_t hostlen)
+{
+ socklen_t slen;
+ int ret;
+
+ slen = sizeof(struct sockaddr_storage);
+ if (getpeername(s, sad, &slen) < 0)
+ return (0);
+ ret = 0;
+ if (getnameinfo((const struct sockaddr *)sad,
+ sad->sa_len, hostip, hostlen,
+ NULL, 0, NI_NUMERICHOST) == 0) {
+ rpctls_verbose_out("rpctls_gethost: %s\n",
+ hostip);
+ ret = 1;
+ }
+ return (ret);
+}
diff --git a/usr.sbin/rpc.tlsservd/rpc.tlscommon.h b/usr.sbin/rpc.tlsservd/rpc.tlscommon.h
new file mode 100644
index 000000000000..ad7147339d51
--- /dev/null
+++ b/usr.sbin/rpc.tlsservd/rpc.tlscommon.h
@@ -0,0 +1,68 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Rick Macklem
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Functions in rpc.tlscommon.c used by both rpc.tlsservd.c and rpc.tlsclntd.c.
+ */
+int rpctls_gethost(int s, struct sockaddr *sad,
+ char *hostip, size_t hostlen);
+int rpctls_checkhost(struct sockaddr *sad, X509 *cert,
+ unsigned int wildcard);
+int rpctls_loadcrlfile(SSL_CTX *ctx);
+void rpctls_checkcrl(void);
+void rpctls_verbose_out(const char *fmt, ...);
+void rpctls_svc_run(void);
+
+/*
+ * A linked list of all current "SSL *"s and socket "fd"s
+ * for kernel RPC TLS connections is maintained.
+ * The "refno" field is a unique 64bit value used to
+ * identify which entry a kernel RPC upcall refers to.
+ */
+LIST_HEAD(ssl_list, ssl_entry);
+struct ssl_entry {
+ LIST_ENTRY(ssl_entry) next;
+ uint64_t refno;
+ int s;
+ bool shutoff;
+ SSL *ssl;
+ X509 *cert;
+};
+
+/* Global variables shared between rpc.tlscommon.c and the daemons. */
+extern int rpctls_debug_level;
+extern bool rpctls_verbose;
+extern SSL_CTX *rpctls_ctx;
+extern const char *rpctls_verify_cafile;
+extern const char *rpctls_verify_capath;
+extern char *rpctls_crlfile;
+extern bool rpctls_cert;
+extern bool rpctls_gothup;
+extern struct ssl_list rpctls_ssllist;
+
diff --git a/usr.sbin/rpc.tlsservd/rpc.tlsservd.8 b/usr.sbin/rpc.tlsservd/rpc.tlsservd.8
new file mode 100644
index 000000000000..9e1c78220884
--- /dev/null
+++ b/usr.sbin/rpc.tlsservd/rpc.tlsservd.8
@@ -0,0 +1,348 @@
+.\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+.\" Authors: Doug Rabson <dfr@rabson.org>
+.\" Developed with Red Inc: Alfred Perlstein <alfred@FreeBSD.org>
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+.\"
+.\" $FreeBSD$
+.\"
+.\" Modified from gssd.8 for rpc.tlsservd.8 by Rick Macklem.
+.Dd January 29, 2021
+.Dt RPC.TLSSERVD 8
+.Os
+.Sh NAME
+.Nm rpc.tlsservd
+.Nd "Sun RPC over TLS Server Daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl D Ar certdir
+.Op Fl d
+.Op Fl h
+.Op Fl l Ar CAfile
+.Op Fl m
+.Op Fl n Ar domain
+.Op Fl p Ar CApath
+.Op Fl r Ar CRLfile
+.Op Fl u
+.Op Fl v
+.Op Fl W
+.Op Fl w
+.Sh DESCRIPTION
+The
+.Nm
+program provides support for the server side of the kernel Sun RPC over TLS
+implementation.
+This daemon must be running to allow the kernel RPC to perform the TLS
+handshake after a TCP client has sent the STARTTLS Null RPC request to
+the server.
+This daemon requires that the kernel be built with
+.Dq options KERNEL_TLS
+and be running on an architecture such as
+.Dq amd64
+that supports a direct map (not i386) with
+.Xr ktls 4
+enabled.
+Note that the
+.Fl tls
+option in the
+.Xr exports 5
+file specifies that the client must use RPC over TLS.
+The
+.Fl tlscert
+option in the
+.Xr exports 5
+file specifies that the client must provide a certificate
+that verifies.
+The
+.Fl tlscertuser
+option in the
+.Xr exports 5
+file specifies that the client must provide a certificate
+that verifies and has a otherName:1.3.6.1.4.1.2238.1.1.1;UTF8: field of
+subjectAltName of the form
+.Dq user@domain
+where
+.Dq domain
+matches the one for this server and
+.Dq user
+is a valid user name that maps to a <uid, gid_list>.
+For the latter two cases, the
+.Fl m
+and either the
+.Fl l
+or
+.Fl p
+options must be specified.
+The
+.Fl tlscertuser
+option also requires that the
+.Fl u
+option on this daemon be specified.
+.Pp
+Also, if the IP address used by the client cannot be trusted,
+the rules in
+.Xr exports 5
+cannot be applied safely.
+As such, the
+.Fl h
+option can be used along with
+.Fl m
+and either the
+.Fl l
+or
+.Fl p
+options to require that the client certificate have the correct
+Fully Qualified Domain Name (FQDN) in it.
+.Pp
+A certificate and associated key must exist in /etc/rpc.tlsservd
+(or the
+.Dq certdir
+specified by the
+.Fl D
+option)
+in files named
+.Dq cert.pem
+and
+.Dq certkey.pem .
+.Pp
+If a SIGHUP signal is sent to the daemon it will reload the
+.Dq CRLfile
+and will shut down any extant connections that presented certificates
+during TLS handshake that have been revoked.
+If the
+.Fl r
+option was not specified, the SIGHUP signal will be ignored.
+.Pp
+The daemon will log failed certificate verifications via
+.Xr syslogd 8
+using LOG_INFO | LOG_DAEMON when the
+.Fl m
+option has been specified.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl D Ar certdir , Fl Fl certdir= Ns Ar certdir
+Use
+.Dq certdir
+instead of /etc/rpc.tlsservd as the location for the
+certificate in a file called
+.Dq cert.pem
+and associated key in
+.Dq certkey.pem .
+.It Fl d , Fl Fl debuglevel
+Run in debug mode.
+In this mode,
+.Nm
+will not fork when it starts.
+.It Fl h , Fl Fl checkhost
+This option specifies that the client must provide a certificate
+that both verifies and has a FQDN that matches the reverse
+DNS name for the IP address that
+the client uses to connect to the server.
+The FQDN should be
+in the DNS field of the subjectAltName, but is also allowed
+to be in the CN field of the
+subjectName in the certificate.
+By default, a wildcard "*" in the FQDN is not allowed.
+With this option, a failure to verify the client certificate
+or match the FQDN will result in the
+server sending AUTH_REJECTEDCRED replies to all client RPCs.
+This option requires the
+.Fl m
+and either the
+.Fl l
+or
+.Fl p
+options.
+.It Fl l Ar CAfile , Fl Fl verifylocs= Ns Ar CAfile
+This option specifies the path name of a CA certificate(s) file
+in pem format, which is used to verify client certificates and to
+set the list of CA(s) sent to the client so that it knows which
+certificate to send to the server during the TLS handshake.
+This path name is used in
+.Dq SSL_CTX_load_verify_locations(ctx,CAfile,NULL)
+and
+.Dq SSL_CTX_set_client_CA_list(ctx,SSL_load_client_CA_file(CAfile))
+openssl library calls.
+Note that this is a path name for the file and is not assumed to be
+in
+.Dq certdir .
+Either this option or the
+.Fl p
+option must be specified when the
+.Fl m
+option is specified so that the daemon can verify the client's
+certificate.
+.It Fl m , Fl Fl mutualverf
+This option specifies that the server is to request a certificate
+from the client during the TLS handshake.
+It does not require that the client provide a certificate.
+It should be specified unless no client doing RPC over TLS is
+required to have a certificate.
+For NFS, either the
+.Xr exports 5
+option
+.Fl tlscert
+or
+.Fl tlscertuser
+may be used to require a client to provide a certificate
+that verifies.
+See
+.Xr exports 5 .
+.It Fl n Ar domain , Fl Fl domain= Ns Ar domain
+This option specifies what the
+.Dq domain
+is for use with the
+.Fl u
+option, overriding the domain taken from the
+.Xr gethostname 2
+of the server this daemon is running on.
+If you have specified the
+.Fl domain
+command line option for
+.Xr nfsuserd 8
+then you should specify this option with the same
+.Dq domain
+that was specified for
+.Xr nfsuserd 8 .
+This option is only meaningful when used with the
+.Fl u
+option.
+.It Fl p Ar CApath , Fl Fl verifydir= Ns Ar CApath
+This option is similar to the
+.Fl l
+option, but specifies the path of a directory with CA
+certificates in it.
+When this option is used,
+.Dq SSL_CTX_set_client_CA_list(ctx,SSL_load_client_CA_file())
+is not called, so a list of CA names might not be passed
+to the client during the TLS handshake.
+.It Fl r Ar CRLfile , Fl Fl crl= Ns Ar CRLfile
+This option specifies a Certificate Revocation List (CRL) file
+that is to be loaded into the verify certificate store and
+checked during verification.
+This option is only meaningful when either the
+.Fl l
+or
+.Fl p
+have been specified.
+.It Fl u , Fl Fl certuser
+This option specifies that if the client provides a certificate
+that both verifies and has a subjectAltName with an otherName
+component of the form
+.Dq otherName:1.3.6.1.4.1.2238.1.1.1;UTF8:user@domain
+where
+.Dq domain
+matches the one for this server,
+then the daemon will attempt to map
+.Dq user
+in the above
+to a user credential <uid, gid_list>.
+There should only be one of these otherName components for each
+.Dq domain .
+If
+.Dq user
+is a valid username in the password database,
+then the <uid, gid_list> for
+.Dq user
+will be used for all
+RPCs on the mount instead of the credentials in the RPC request
+header.
+This option requires the
+.Fl m
+and either the
+.Fl l
+or
+.Fl p
+options.
+Use of this option might not conform to RFC-NNNN, which does
+not allow certificates to be used for user authentication.
+.It Fl v , Fl Fl verbose
+Run in verbose mode.
+In this mode,
+.Nm
+will log activity messages to
+.Xr syslogd 8
+using LOG_INFO | LOG_DAEMON or to
+stderr, if the
+.Fl d
+option has also been specified.
+.It Fl W , Fl Fl multiwild
+This option is used with the
+.Fl h
+option to allow use of a wildcard
+.Dq *
+that matches multiple
+components of the reverse DNS name for the client's IP
+address.
+For example, the FQDN
+.Dq *.uoguelph.ca
+would match both
+.Dq laptop21.uoguelph.ca
+and
+.Dq laptop3.cis.uoguelph.ca .
+.It Fl w , Fl Fl singlewild
+Similar to
+.Fl W
+but allows the wildcard
+.Dq *
+to match a single component of the reverse DNS name.
+For example, the FQDN
+.Dq *.uoguelph.ca
+would match
+.Dq laptop21.uoguelph.ca
+but not
+.Dq laptop3.cis.uoguelph.ca .
+Only one of the
+.Fl W
+and
+.Fl w
+options is allowed.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr openssl 1 ,
+.Xr ktls 4 ,
+.Xr exports 5 ,
+.Xr mount_nfs 8 ,
+.Xr nfsuserd 8 ,
+.Xr rpc.tlsclntd 8 ,
+.Xr syslogd 8
+.Sh STANDARDS
+The implementation is based on the specification in
+.Rs
+.%B "RFC NNNN"
+.%T "Towards Remote Procedure Call Encryption By Default"
+.Re
+.Sh HISTORY
+The
+.Nm
+manual page first appeared in
+.Fx 13.0 .
+.Sh BUGS
+This daemon cannot be safely shut down and restarted if there are
+any active RPC-over-TLS connections.
+Doing so will orphan the KERNEL_TLS connections, so that they
+can no longer do upcalls successfully, since the
+.Dq SSL *
+structures in userspace have been lost.
diff --git a/usr.sbin/rpc.tlsservd/rpc.tlsservd.c b/usr.sbin/rpc.tlsservd/rpc.tlsservd.c
new file mode 100644
index 000000000000..1c7687cad87a
--- /dev/null
+++ b/usr.sbin/rpc.tlsservd/rpc.tlsservd.c
@@ -0,0 +1,886 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr@rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+/*
+ * Extensively modified from /usr/src/usr.sbin/gssd.c r344402 for
+ * the server side of kernel RPC-over-TLS by Rick Macklem.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/syslog.h>
+#include <sys/time.h>
+#include <err.h>
+#include <getopt.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#include <rpc/rpcsec_tls.h>
+
+#include <openssl/opensslconf.h>
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#include "rpctlssd.h"
+#include "rpc.tlscommon.h"
+
+#ifndef _PATH_RPCTLSSDSOCK
+#define _PATH_RPCTLSSDSOCK "/var/run/rpc.tlsservd.sock"
+#endif
+#ifndef _PATH_CERTANDKEY
+#define _PATH_CERTANDKEY "/etc/rpc.tlsservd/"
+#endif
+#ifndef _PATH_RPCTLSSDPID
+#define _PATH_RPCTLSSDPID "/var/run/rpc.tlsservd.pid"
+#endif
+#ifndef _PREFERRED_CIPHERS
+#define _PREFERRED_CIPHERS "AES128-GCM-SHA256"
+#endif
+
+/* Global variables also used by rpc.tlscommon.c. */
+int rpctls_debug_level;
+bool rpctls_verbose;
+SSL_CTX *rpctls_ctx = NULL;
+const char *rpctls_verify_cafile = NULL;
+const char *rpctls_verify_capath = NULL;
+char *rpctls_crlfile = NULL;
+bool rpctls_gothup = false;
+struct ssl_list rpctls_ssllist;
+
+static struct pidfh *rpctls_pfh = NULL;
+static bool rpctls_do_mutual = false;
+static const char *rpctls_certdir = _PATH_CERTANDKEY;
+static bool rpctls_comparehost = false;
+static unsigned int rpctls_wildcard = X509_CHECK_FLAG_NO_WILDCARDS;
+static uint64_t rpctls_ssl_refno = 0;
+static uint64_t rpctls_ssl_sec = 0;
+static uint64_t rpctls_ssl_usec = 0;
+static bool rpctls_cnuser = false;
+static char *rpctls_dnsname;
+static const char *rpctls_cnuseroid = "1.3.6.1.4.1.2238.1.1.1";
+
+static void rpctlssd_terminate(int);
+static SSL_CTX *rpctls_setup_ssl(const char *certdir);
+static SSL *rpctls_server(SSL_CTX *ctx, int s,
+ uint32_t *flags, uint32_t *uidp,
+ int *ngrps, uint32_t *gidp, X509 **certp);
+static int rpctls_cnname(X509 *cert, uint32_t *uidp,
+ int *ngrps, uint32_t *gidp);
+static char *rpctls_getdnsname(char *dnsname);
+static void rpctls_huphandler(int sig __unused);
+
+extern void rpctlssd_1(struct svc_req *rqstp, SVCXPRT *transp);
+
+static struct option longopts[] = {
+ { "certdir", required_argument, NULL, 'D' },
+ { "debuglevel", no_argument, NULL, 'd' },
+ { "checkhost", no_argument, NULL, 'h' },
+ { "verifylocs", required_argument, NULL, 'l' },
+ { "mutualverf", no_argument, NULL, 'm' },
+ { "domain", required_argument, NULL, 'n' },
+ { "verifydir", required_argument, NULL, 'p' },
+ { "crl", required_argument, NULL, 'r' },
+ { "certuser", no_argument, NULL, 'u' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "multiwild", no_argument, NULL, 'W' },
+ { "singlewild", no_argument, NULL, 'w' },
+ { NULL, 0, NULL, 0 }
+};
+
+int
+main(int argc, char **argv)
+{
+ /*
+ * We provide an RPC service on a local-domain socket. The
+ * kernel rpctls code will upcall to this daemon to do the initial
+ * TLS handshake.
+ */
+ struct sockaddr_un sun;
+ int ch, debug, fd, oldmask;
+ SVCXPRT *xprt;
+ struct timeval tm;
+ struct timezone tz;
+ char hostname[MAXHOSTNAMELEN + 2];
+ pid_t otherpid;
+ bool tls_enable;
+ size_t tls_enable_len;
+
+ /* Check that another rpctlssd isn't already running. */
+ rpctls_pfh = pidfile_open(_PATH_RPCTLSSDPID, 0600, &otherpid);
+ if (rpctls_pfh == NULL) {
+ if (errno == EEXIST)
+ errx(1, "rpctlssd already running, pid: %d.", otherpid);
+ warn("cannot open or create pidfile");
+ }
+
+ /* Check to see that the ktls is enabled. */
+ tls_enable_len = sizeof(tls_enable);
+ if (sysctlbyname("kern.ipc.tls.enable", &tls_enable, &tls_enable_len,
+ NULL, 0) != 0 || !tls_enable)
+ errx(1, "Kernel TLS not enabled");
+
+ /* Get the time when this daemon is started. */
+ gettimeofday(&tm, &tz);
+ rpctls_ssl_sec = tm.tv_sec;
+ rpctls_ssl_usec = tm.tv_usec;
+
+ /* Set the dns name for the server. */
+ rpctls_dnsname = rpctls_getdnsname(hostname);
+ if (rpctls_dnsname == NULL) {
+ strcpy(hostname, "@default.domain");
+ rpctls_dnsname = hostname;
+ }
+
+ debug = 0;
+ rpctls_verbose = false;
+ while ((ch = getopt_long(argc, argv, "D:dhl:n:mp:r:uvWw", longopts,
+ NULL)) != -1) {
+ switch (ch) {
+ case 'D':
+ rpctls_certdir = optarg;
+ break;
+ case 'd':
+ rpctls_debug_level++;
+ break;
+ case 'h':
+ rpctls_comparehost = true;
+ break;
+ case 'l':
+ rpctls_verify_cafile = optarg;
+ break;
+ case 'm':
+ rpctls_do_mutual = true;
+ break;
+ case 'n':
+ hostname[0] = '@';
+ strlcpy(&hostname[1], optarg, MAXHOSTNAMELEN + 1);
+ rpctls_dnsname = hostname;
+ break;
+ case 'p':
+ rpctls_verify_capath = optarg;
+ break;
+ case 'r':
+ rpctls_crlfile = optarg;
+ break;
+ case 'u':
+ rpctls_cnuser = true;
+ break;
+ case 'v':
+ rpctls_verbose = true;
+ break;
+ case 'W':
+ if (rpctls_wildcard != X509_CHECK_FLAG_NO_WILDCARDS)
+ errx(1, "options -w and -W are mutually "
+ "exclusive");
+ rpctls_wildcard = X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS;
+ break;
+ case 'w':
+ if (rpctls_wildcard != X509_CHECK_FLAG_NO_WILDCARDS)
+ errx(1, "options -w and -W are mutually "
+ "exclusive");
+ rpctls_wildcard = 0;
+ break;
+ default:
+ fprintf(stderr, "usage: %s "
+ "[-D/--certdir certdir] [-d/--debuglevel] "
+ "[-h/--checkhost] "
+ "[-l/--verifylocs CAfile] [-m/--mutualverf] "
+ "[-n/--domain domain_name] "
+ "[-p/--verifydir CApath] [-r/--crl CRLfile] "
+ "[-u/--certuser] [-v/--verbose] [-W/--multiwild] "
+ "[-w/--singlewild]\n", argv[0]);
+ exit(1);
+ }
+ }
+ if (rpctls_do_mutual && rpctls_verify_cafile == NULL &&
+ rpctls_verify_capath == NULL)
+ errx(1, "-m requires the -l <CAfile> and/or "
+ "-p <CApath> options");
+ if (rpctls_comparehost && (!rpctls_do_mutual ||
+ (rpctls_verify_cafile == NULL && rpctls_verify_capath == NULL)))
+ errx(1, "-h requires the -m plus the "
+ "-l <CAfile> and/or -p <CApath> options");
+ if (!rpctls_comparehost && rpctls_wildcard !=
+ X509_CHECK_FLAG_NO_WILDCARDS)
+ errx(1, "The -w or -W options require the -h option");
+ if (rpctls_cnuser && (!rpctls_do_mutual ||
+ (rpctls_verify_cafile == NULL && rpctls_verify_capath == NULL)))
+ errx(1, "-u requires the -m plus the "
+ "-l <CAfile> and/or -p <CApath> options");
+
+ if (modfind("krpc") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("krpc") < 0 || modfind("krpc") < 0)
+ errx(1, "Kernel RPC is not available");
+ }
+
+ if (rpctls_debug_level == 0) {
+ if (daemon(0, 0) != 0)
+ err(1, "Can't daemonize");
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ }
+ signal(SIGTERM, rpctlssd_terminate);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, rpctls_huphandler);
+
+ pidfile_write(rpctls_pfh);
+
+ memset(&sun, 0, sizeof sun);
+ sun.sun_family = AF_LOCAL;
+ unlink(_PATH_RPCTLSSDSOCK);
+ strcpy(sun.sun_path, _PATH_RPCTLSSDSOCK);
+ sun.sun_len = SUN_LEN(&sun);
+ fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR, "Can't create local rpctlssd socket");
+ exit(1);
+ }
+ err(1, "Can't create local rpctlssd socket");
+ }
+ oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+ if (bind(fd, (struct sockaddr *)&sun, sun.sun_len) < 0) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR, "Can't bind local rpctlssd socket");
+ exit(1);
+ }
+ err(1, "Can't bind local rpctlssd socket");
+ }
+ umask(oldmask);
+ if (listen(fd, SOMAXCONN) < 0) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't listen on local rpctlssd socket");
+ exit(1);
+ }
+ err(1, "Can't listen on local rpctlssd socket");
+ }
+ xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+ if (!xprt) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't create transport for local rpctlssd socket");
+ exit(1);
+ }
+ err(1, "Can't create transport for local rpctlssd socket");
+ }
+ if (!svc_reg(xprt, RPCTLSSD, RPCTLSSDVERS, rpctlssd_1, NULL)) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't register service for local rpctlssd socket");
+ exit(1);
+ }
+ err(1, "Can't register service for local rpctlssd socket");
+ }
+
+ rpctls_ctx = rpctls_setup_ssl(rpctls_certdir);
+ if (rpctls_ctx == NULL) {
+ if (rpctls_debug_level == 0) {
+ syslog(LOG_ERR, "Can't create SSL context");
+ exit(1);
+ }
+ err(1, "Can't create SSL context");
+ }
+ rpctls_gothup = false;
+ LIST_INIT(&rpctls_ssllist);
+
+ rpctls_syscall(RPCTLS_SYSC_SRVSETPATH, _PATH_RPCTLSSDSOCK);
+
+ rpctls_svc_run();
+
+ rpctls_syscall(RPCTLS_SYSC_SRVSHUTDOWN, "");
+
+ SSL_CTX_free(rpctls_ctx);
+ EVP_cleanup();
+ return (0);
+}
+
+bool_t
+rpctlssd_null_1_svc(__unused void *argp, __unused void *result,
+ __unused struct svc_req *rqstp)
+{
+
+ rpctls_verbose_out("rpctlssd_null_svc: done\n");
+ return (TRUE);
+}
+
+bool_t
+rpctlssd_connect_1_svc(__unused void *argp,
+ struct rpctlssd_connect_res *result, __unused struct svc_req *rqstp)
+{
+ int ngrps, s;
+ SSL *ssl;
+ uint32_t flags;
+ struct ssl_entry *newslp;
+ uint32_t uid;
+ uint32_t *gidp;
+ X509 *cert;
+
+ rpctls_verbose_out("rpctlsd_connect_svc: started\n");
+ memset(result, 0, sizeof(*result));
+ /* Get the socket fd from the kernel. */
+ s = rpctls_syscall(RPCTLS_SYSC_SRVSOCKET, "");
+ if (s < 0)
+ return (FALSE);
+
+ /* Do the server side of a TLS handshake. */
+ gidp = calloc(NGROUPS, sizeof(*gidp));
+ ssl = rpctls_server(rpctls_ctx, s, &flags, &uid, &ngrps, gidp, &cert);
+ if (ssl == NULL) {
+ free(gidp);
+ rpctls_verbose_out("rpctlssd_connect_svc: ssl "
+ "accept failed\n");
+ /*
+ * For RPC-over-TLS, this upcall is expected
+ * to close off the socket upon handshake failure.
+ */
+ close(s);
+ return (FALSE);
+ } else {
+ rpctls_verbose_out("rpctlssd_connect_svc: "
+ "succeeded flags=0x%x\n", flags);
+ result->flags = flags;
+ result->sec = rpctls_ssl_sec;
+ result->usec = rpctls_ssl_usec;
+ result->ssl = ++rpctls_ssl_refno;
+ /* Hard to believe this could ever wrap around.. */
+ if (rpctls_ssl_refno == 0)
+ result->ssl = ++rpctls_ssl_refno;
+ if ((flags & RPCTLS_FLAGS_CERTUSER) != 0) {
+ result->uid = uid;
+ result->gid.gid_len = ngrps;
+ result->gid.gid_val = gidp;
+ } else {
+ result->uid = 0;
+ result->gid.gid_len = 0;
+ result->gid.gid_val = gidp;
+ }
+ }
+
+ /* Maintain list of all current SSL *'s */
+ newslp = malloc(sizeof(*newslp));
+ newslp->ssl = ssl;
+ newslp->s = s;
+ newslp->shutoff = false;
+ newslp->refno = rpctls_ssl_refno;
+ newslp->cert = cert;
+ LIST_INSERT_HEAD(&rpctls_ssllist, newslp, next);
+ return (TRUE);
+}
+
+bool_t
+rpctlssd_handlerecord_1_svc(struct rpctlssd_handlerecord_arg *argp,
+ struct rpctlssd_handlerecord_res *result, __unused struct svc_req *rqstp)
+{
+ struct ssl_entry *slp;
+ int ret;
+ char junk;
+
+ slp = NULL;
+ if (argp->sec == rpctls_ssl_sec && argp->usec ==
+ rpctls_ssl_usec) {
+ LIST_FOREACH(slp, &rpctls_ssllist, next) {
+ if (slp->refno == argp->ssl)
+ break;
+ }
+ }
+
+ if (slp != NULL) {
+ rpctls_verbose_out("rpctlssd_handlerecord fd=%d\n",
+ slp->s);
+ /*
+ * An SSL_read() of 0 bytes should fail, but it should
+ * handle the non-application data record before doing so.
+ */
+ ret = SSL_read(slp->ssl, &junk, 0);
+ if (ret <= 0) {
+ /* Check to see if this was a close alert. */
+ ret = SSL_get_shutdown(slp->ssl);
+ if ((ret & (SSL_SENT_SHUTDOWN |
+ SSL_RECEIVED_SHUTDOWN)) == SSL_RECEIVED_SHUTDOWN)
+ SSL_shutdown(slp->ssl);
+ } else {
+ if (rpctls_debug_level == 0)
+ syslog(LOG_ERR, "SSL_read returned %d", ret);
+ else
+ fprintf(stderr, "SSL_read returned %d\n", ret);
+ }
+ result->reterr = RPCTLSERR_OK;
+ } else
+ result->reterr = RPCTLSERR_NOSSL;
+ return (TRUE);
+}
+
+bool_t
+rpctlssd_disconnect_1_svc(struct rpctlssd_disconnect_arg *argp,
+ struct rpctlssd_disconnect_res *result, __unused struct svc_req *rqstp)
+{
+ struct ssl_entry *slp;
+ int ret;
+
+ slp = NULL;
+ if (argp->sec == rpctls_ssl_sec && argp->usec ==
+ rpctls_ssl_usec) {
+ LIST_FOREACH(slp, &rpctls_ssllist, next) {
+ if (slp->refno == argp->ssl)
+ break;
+ }
+ }
+
+ if (slp != NULL) {
+ rpctls_verbose_out("rpctlssd_disconnect fd=%d closed\n",
+ slp->s);
+ LIST_REMOVE(slp, next);
+ if (!slp->shutoff) {
+ ret = SSL_get_shutdown(slp->ssl);
+ /*
+ * Do an SSL_shutdown() unless a close alert has
+ * already been sent.
+ */
+ if ((ret & SSL_SENT_SHUTDOWN) == 0)
+ SSL_shutdown(slp->ssl);
+ }
+ SSL_free(slp->ssl);
+ if (slp->cert != NULL)
+ X509_free(slp->cert);
+ /*
+ * For RPC-over-TLS, this upcall is expected
+ * to close off the socket.
+ */
+ if (!slp->shutoff)
+ shutdown(slp->s, SHUT_WR);
+ close(slp->s);
+ free(slp);
+ result->reterr = RPCTLSERR_OK;
+ } else
+ result->reterr = RPCTLSERR_NOCLOSE;
+ return (TRUE);
+}
+
+int
+rpctlssd_1_freeresult(__unused SVCXPRT *transp, xdrproc_t xdr_result,
+ caddr_t result)
+{
+ rpctlssd_connect_res *res;
+
+ if (xdr_result == (xdrproc_t)xdr_rpctlssd_connect_res) {
+ res = (rpctlssd_connect_res *)(void *)result;
+ free(res->gid.gid_val);
+ }
+ return (TRUE);
+}
+
+static void
+rpctlssd_terminate(int sig __unused)
+{
+ struct ssl_entry *slp;
+
+ rpctls_syscall(RPCTLS_SYSC_SRVSHUTDOWN, "");
+ pidfile_remove(rpctls_pfh);
+
+ LIST_FOREACH(slp, &rpctls_ssllist, next)
+ shutdown(slp->s, SHUT_RD);
+ exit(0);
+}
+
+/* Allow the handshake to proceed. */
+static int
+rpctls_verify_callback(__unused int preverify_ok,
+ __unused X509_STORE_CTX *x509_ctx)
+{
+
+ return (1);
+}
+
+static SSL_CTX *
+rpctls_setup_ssl(const char *certdir)
+{
+ SSL_CTX *ctx;
+ char path[PATH_MAX];
+ size_t len, rlen;
+ int ret;
+
+ SSL_library_init();
+ SSL_load_error_strings();
+ OpenSSL_add_all_algorithms();
+
+ ctx = SSL_CTX_new(TLS_server_method());
+ if (ctx == NULL) {
+ rpctls_verbose_out("rpctls_setup_ssl: SSL_CTX_new failed\n");
+ return (NULL);
+ }
+ SSL_CTX_set_ecdh_auto(ctx, 1);
+
+ /*
+ * Set preferred ciphers, since KERN_TLS only supports a
+ * few of them.
+ */
+ ret = SSL_CTX_set_cipher_list(ctx, _PREFERRED_CIPHERS);
+ if (ret == 0) {
+ rpctls_verbose_out("rpctls_setup_ssl: "
+ "SSL_CTX_set_cipher_list failed to set any ciphers\n");
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+
+ /* Get the cert.pem and certkey.pem files from the directory certdir. */
+ len = strlcpy(path, certdir, sizeof(path));
+ rlen = sizeof(path) - len;
+ if (strlcpy(&path[len], "cert.pem", rlen) != 8) {
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ ret = SSL_CTX_use_certificate_file(ctx, path, SSL_FILETYPE_PEM);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_setup_ssl: can't use certificate "
+ "file path=%s ret=%d\n", path, ret);
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ if (strlcpy(&path[len], "certkey.pem", rlen) != 11) {
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ ret = SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_setup_ssl: Can't use private "
+ "key path=%s ret=%d\n", path, ret);
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+
+ /* Set Mutual authentication, as required. */
+ if (rpctls_do_mutual) {
+ if (rpctls_verify_cafile != NULL ||
+ rpctls_verify_capath != NULL) {
+ if (rpctls_crlfile != NULL) {
+ ret = rpctls_loadcrlfile(ctx);
+ if (ret == 0) {
+ rpctls_verbose_out("rpctls_setup_ssl:"
+ " Load CRLfile failed\n");
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ }
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+ ret = 1;
+ if (rpctls_verify_cafile != NULL)
+ ret = SSL_CTX_load_verify_file(ctx,
+ rpctls_verify_cafile);
+ if (ret != 0 && rpctls_verify_capath != NULL)
+ ret = SSL_CTX_load_verify_dir(ctx,
+ rpctls_verify_capath);
+#else
+ ret = SSL_CTX_load_verify_locations(ctx,
+ rpctls_verify_cafile, rpctls_verify_capath);
+#endif
+ if (ret == 0) {
+ rpctls_verbose_out("rpctls_setup_ssl: "
+ "Can't load verify locations\n");
+ SSL_CTX_free(ctx);
+ return (NULL);
+ }
+ if (rpctls_verify_cafile != NULL)
+ SSL_CTX_set_client_CA_list(ctx,
+ SSL_load_client_CA_file(
+ rpctls_verify_cafile));
+ }
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
+ rpctls_verify_callback);
+ }
+ SSL_CTX_clear_mode(ctx, SSL_MODE_NO_KTLS_TX | SSL_MODE_NO_KTLS_RX);
+ return (ctx);
+}
+
+static SSL *
+rpctls_server(SSL_CTX *ctx, int s, uint32_t *flags, uint32_t *uidp,
+ int *ngrps, uint32_t *gidp, X509 **certp)
+{
+ SSL *ssl;
+ X509 *cert;
+ struct sockaddr *sad;
+ struct sockaddr_storage ad;
+ char hostnam[NI_MAXHOST];
+ int gethostret, ret;
+ char *cp, *cp2;
+ long verfret;
+
+ *flags = 0;
+ *certp = NULL;
+ sad = (struct sockaddr *)&ad;
+ ssl = SSL_new(ctx);
+ if (ssl == NULL) {
+ rpctls_verbose_out("rpctls_server: SSL_new failed\n");
+ return (NULL);
+ }
+ if (SSL_set_fd(ssl, s) != 1) {
+ rpctls_verbose_out("rpctls_server: SSL_set_fd failed\n");
+ SSL_free(ssl);
+ return (NULL);
+ }
+ ret = SSL_accept(ssl);
+ if (ret != 1) {
+ rpctls_verbose_out("rpctls_server: SSL_accept "
+ "failed ret=%d\n", ret);
+ SSL_free(ssl);
+ return (NULL);
+ }
+ *flags |= RPCTLS_FLAGS_HANDSHAKE;
+ if (rpctls_do_mutual) {
+ cert = SSL_get_peer_certificate(ssl);
+ if (cert != NULL) {
+ gethostret = rpctls_gethost(s, sad, hostnam,
+ sizeof(hostnam));
+ if (gethostret == 0)
+ hostnam[0] = '\0';
+ cp2 = X509_NAME_oneline(
+ X509_get_subject_name(cert), NULL, 0);
+ *flags |= RPCTLS_FLAGS_GOTCERT;
+ verfret = SSL_get_verify_result(ssl);
+ if (verfret != X509_V_OK) {
+ cp = X509_NAME_oneline(
+ X509_get_issuer_name(cert), NULL, 0);
+ if (rpctls_debug_level == 0)
+ syslog(LOG_INFO | LOG_DAEMON,
+ "rpctls_server: client IP %s "
+ "issuerName=%s subjectName=%s"
+ " verify failed %s\n", hostnam,
+ cp, cp2,
+ X509_verify_cert_error_string(
+ verfret));
+ else
+ fprintf(stderr,
+ "rpctls_server: client IP %s "
+ "issuerName=%s subjectName=%s"
+ " verify failed %s\n", hostnam,
+ cp, cp2,
+ X509_verify_cert_error_string(
+ verfret));
+ }
+ if (verfret ==
+ X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
+ verfret == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)
+ *flags |= RPCTLS_FLAGS_SELFSIGNED;
+ else if (verfret == X509_V_OK) {
+ if (rpctls_comparehost) {
+ ret = 0;
+ if (gethostret != 0)
+ ret = rpctls_checkhost(sad,
+ cert, rpctls_wildcard);
+ if (ret != 1) {
+ *flags |=
+ RPCTLS_FLAGS_DISABLED;
+ rpctls_verbose_out(
+ "rpctls_server: "
+ "checkhost "
+ "failed\n");
+ }
+ }
+ if (rpctls_cnuser) {
+ ret = rpctls_cnname(cert, uidp,
+ ngrps, gidp);
+ if (ret != 0)
+ *flags |= RPCTLS_FLAGS_CERTUSER;
+ }
+ *flags |= RPCTLS_FLAGS_VERIFIED;
+ *certp = cert;
+ cert = NULL;
+ }
+ if (cert != NULL)
+ X509_free(cert);
+ } else
+ rpctls_verbose_out("rpctls_server: "
+ "No peer certificate\n");
+ }
+
+ /* Check to see that ktls is working for the connection. */
+ ret = BIO_get_ktls_send(SSL_get_wbio(ssl));
+ rpctls_verbose_out("rpctls_server: BIO_get_ktls_send=%d\n", ret);
+ if (ret != 0) {
+ ret = BIO_get_ktls_recv(SSL_get_rbio(ssl));
+ rpctls_verbose_out("rpctls_server: BIO_get_ktls_recv=%d\n",
+ ret);
+ }
+ if (ret == 0) {
+ if (rpctls_debug_level == 0)
+ syslog(LOG_ERR, "ktls not working");
+ else
+ fprintf(stderr, "ktls not working\n");
+ /*
+ * The handshake has completed, so all that can be
+ * done is disable the connection.
+ */
+ *flags |= RPCTLS_FLAGS_DISABLED;
+ }
+
+ return (ssl);
+}
+
+/*
+ * Acquire the dnsname for this server.
+ */
+static char *
+rpctls_getdnsname(char *hostname)
+{
+ char *cp, *dnsname;
+ struct addrinfo *aip, hints;
+ int error;
+
+ dnsname = NULL;
+ if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
+ if ((cp = strchr(hostname, '.')) != NULL &&
+ *(cp + 1) != '\0') {
+ *cp = '@';
+ dnsname = cp;
+ } else {
+ memset((void *)&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ error = getaddrinfo(hostname, NULL, &hints, &aip);
+ if (error == 0) {
+ if (aip->ai_canonname != NULL &&
+ (cp = strchr(aip->ai_canonname, '.')) !=
+ NULL && *(cp + 1) != '\0') {
+ hostname[0] = '@';
+ strlcpy(&hostname[1], cp + 1,
+ MAXHOSTNAMELEN + 1);
+ dnsname = hostname;
+ }
+ freeaddrinfo(aip);
+ }
+ }
+ }
+ return (dnsname);
+}
+
+/*
+ * Check for an otherName component of subjectAltName where the OID
+ * matches and the "domain" matches that of this server.
+ * If found, map "user" to a <uid, gidlist> for it.
+ */
+static int
+rpctls_cnname(X509 *cert, uint32_t *uidp, int *ngrps, uint32_t *gidp)
+{
+ char *cp, usern[1024 + 1];
+ struct passwd *pwd;
+ gid_t gids[NGROUPS];
+ int i, j;
+ GENERAL_NAMES *genlist;
+ GENERAL_NAME *genname;
+ OTHERNAME *val;
+ size_t slen;
+
+ /* First, find the otherName in the subjectAltName. */
+ genlist = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+ if (genlist == NULL)
+ return (0);
+ cp = NULL;
+ for (i = 0; i < sk_GENERAL_NAME_num(genlist); i++) {
+ genname = sk_GENERAL_NAME_value(genlist, i);
+ if (genname->type != GEN_OTHERNAME)
+ continue;
+ val = genname->d.otherName;
+
+ /* Check to see that it is the correct OID. */
+ slen = i2t_ASN1_OBJECT(usern, sizeof(usern), val->type_id);
+ if (slen != strlen(rpctls_cnuseroid) || memcmp(usern,
+ rpctls_cnuseroid, slen) != 0)
+ continue;
+
+ /* Sanity check the otherName. */
+ if (val->value->type != V_ASN1_UTF8STRING ||
+ val->value->value.utf8string->length < 3 ||
+ (size_t)val->value->value.utf8string->length > sizeof(usern)
+ - 1) {
+ rpctls_verbose_out("rpctls_cnname: invalid cnuser "
+ "type=%d\n", val->value->type);
+ continue;
+ }
+
+ /* Look for a "user" in the otherName */
+ memcpy(usern, val->value->value.utf8string->data,
+ val->value->value.utf8string->length);
+ usern[val->value->value.utf8string->length] = '\0';
+
+ /* Now, look for the @dnsname suffix in the commonName. */
+ cp = strcasestr(usern, rpctls_dnsname);
+ if (cp == NULL)
+ continue;
+ if (*(cp + strlen(rpctls_dnsname)) != '\0') {
+ cp = NULL;
+ continue;
+ }
+ *cp = '\0';
+ break;
+ }
+ if (cp == NULL)
+ return (0);
+
+ /* See if the "user" is in the passwd database. */
+ pwd = getpwnam(usern);
+ if (pwd == NULL)
+ return (0);
+ *uidp = pwd->pw_uid;
+ *ngrps = NGROUPS;
+ if (getgrouplist(pwd->pw_name, pwd->pw_gid, gids, ngrps) < 0)
+ return (0);
+ rpctls_verbose_out("mapped user=%s ngrps=%d uid=%d\n", pwd->pw_name,
+ *ngrps, pwd->pw_uid);
+ for (j = 0; j < *ngrps; j++)
+ gidp[j] = gids[j];
+ return (1);
+}
+
+static void
+rpctls_huphandler(int sig __unused)
+{
+
+ rpctls_gothup = true;
+}