aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/rpc.tlsservd/rpc.tlscommon.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/rpc.tlsservd/rpc.tlscommon.c')
-rw-r--r--usr.sbin/rpc.tlsservd/rpc.tlscommon.c293
1 files changed, 293 insertions, 0 deletions
diff --git a/usr.sbin/rpc.tlsservd/rpc.tlscommon.c b/usr.sbin/rpc.tlsservd/rpc.tlscommon.c
new file mode 100644
index 000000000000..5bf7f86fba23
--- /dev/null
+++ b/usr.sbin/rpc.tlsservd/rpc.tlscommon.c
@@ -0,0 +1,293 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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>
+#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);
+}