1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2021 Rick Macklem
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/queue.h>
33#include <sys/syslog.h>
34#include <sys/select.h>
35#include <sys/time.h>
36
37#include <netdb.h>
38#include <signal.h>
39#include <stdarg.h>
40#include <stdbool.h>
41#include <string.h>
42
43#include <rpc/rpc.h>
44
45#include <openssl/opensslconf.h>
46#include <openssl/bio.h>
47#include <openssl/ssl.h>
48#include <openssl/err.h>
49#include <openssl/x509v3.h>
50
51#include "rpc.tlscommon.h"
52
53/*
54 * How long to delay a reload of the CRL when there are RPC request(s)
55 * to process, in usec.  Must be less than 1second.
56 */
57#define	RELOADDELAY	250000
58
59void
60rpctls_svc_run(void)
61{
62	int ret;
63	struct timeval tv;
64	fd_set readfds;
65	uint64_t curtime, nexttime;
66	struct timespec tp;
67	sigset_t sighup_mask;
68
69	/* Expand svc_run() here so that we can call rpctls_loadcrlfile(). */
70	curtime = nexttime = 0;
71	sigemptyset(&sighup_mask);
72	sigaddset(&sighup_mask, SIGHUP);
73	for (;;) {
74		clock_gettime(CLOCK_MONOTONIC, &tp);
75		curtime = tp.tv_sec;
76		curtime = curtime * 1000000 + tp.tv_nsec / 1000;
77		sigprocmask(SIG_BLOCK, &sighup_mask, NULL);
78		if (rpctls_gothup && curtime >= nexttime) {
79			rpctls_gothup = false;
80			sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
81			ret = rpctls_loadcrlfile(rpctls_ctx);
82			if (ret != 0)
83				rpctls_checkcrl();
84			else
85				rpctls_verbose_out("rpc.tlsservd: Can't "
86				    "reload CRLfile\n");
87			clock_gettime(CLOCK_MONOTONIC, &tp);
88			nexttime = tp.tv_sec;
89			nexttime = nexttime * 1000000 + tp.tv_nsec / 1000 +
90			    RELOADDELAY;
91		} else
92			sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
93
94		/*
95		 * If a reload is pending, poll for received request(s),
96		 * otherwise set a RELOADDELAY timeout, since a SIGHUP
97		 * could be processed between the got_sighup test and
98		 * the select() system call.
99		 */
100		tv.tv_sec = 0;
101		if (rpctls_gothup)
102			tv.tv_usec = 0;
103		else
104			tv.tv_usec = RELOADDELAY;
105		readfds = svc_fdset;
106		switch (select(svc_maxfd + 1, &readfds, NULL, NULL, &tv)) {
107		case -1:
108			if (errno == EINTR) {
109				/* Allow a reload now. */
110				nexttime = 0;
111				continue;
112			}
113			syslog(LOG_ERR, "rpc.tls daemon died: select: %m");
114			exit(1);
115		case 0:
116			/* Allow a reload now. */
117			nexttime = 0;
118			continue;
119		default:
120			svc_getreqset(&readfds);
121		}
122	}
123}
124
125/*
126 * (re)load the CRLfile into the certificate verification store.
127 */
128int
129rpctls_loadcrlfile(SSL_CTX *ctx)
130{
131	X509_STORE *certstore;
132	X509_LOOKUP *certlookup;
133	int ret;
134
135	if ((rpctls_verify_cafile != NULL ||
136	    rpctls_verify_capath != NULL) &&
137	    rpctls_crlfile != NULL) {
138		certstore = SSL_CTX_get_cert_store(ctx);
139		certlookup = X509_STORE_add_lookup(
140		    certstore, X509_LOOKUP_file());
141		ret = 0;
142		if (certlookup != NULL)
143			ret = X509_load_crl_file(certlookup,
144			    rpctls_crlfile, X509_FILETYPE_PEM);
145		if (ret != 0)
146			ret = X509_STORE_set_flags(certstore,
147			    X509_V_FLAG_CRL_CHECK |
148			    X509_V_FLAG_CRL_CHECK_ALL);
149		if (ret == 0) {
150			rpctls_verbose_out(
151			    "rpctls_loadcrlfile: Can't"
152			    " load CRLfile=%s\n",
153			    rpctls_crlfile);
154			return (ret);
155		}
156	}
157	return (1);
158}
159
160/*
161 * Read the CRL file and check for any extant connections
162 * that might now be revoked.
163 */
164void
165rpctls_checkcrl(void)
166{
167	struct ssl_entry *slp;
168	BIO *infile;
169	X509_CRL *crl;
170	X509_REVOKED *revoked;
171	char *cp, *cp2, nullstr[1];
172	int ret;
173
174	if (rpctls_crlfile == NULL || (rpctls_verify_cafile == NULL &&
175	    rpctls_verify_capath == NULL))
176		return;
177	infile = BIO_new(BIO_s_file());
178	if (infile == NULL) {
179		rpctls_verbose_out("rpctls_checkcrl: Cannot BIO_new\n");
180		return;
181	}
182	ret = BIO_read_filename(infile, rpctls_crlfile);
183	if (ret != 1) {
184		rpctls_verbose_out("rpctls_checkcrl: Cannot read CRL file\n");
185		BIO_free(infile);
186		return;
187	}
188
189	nullstr[0] = '\0';
190	for (crl = PEM_read_bio_X509_CRL(infile, NULL, NULL, nullstr);
191	    crl != NULL; crl = PEM_read_bio_X509_CRL(infile, NULL, NULL,
192	    nullstr)) {
193		LIST_FOREACH(slp, &rpctls_ssllist, next) {
194			if (slp->cert != NULL) {
195				ret = X509_CRL_get0_by_cert(crl, &revoked,
196				    slp->cert);
197				/*
198				 * Do a shutdown on the socket, so that it
199				 * can no longer be used.  The kernel RPC
200				 * code will notice the socket is disabled
201				 * and will do a disconnect upcall, which will
202				 * close the socket.
203				 */
204				if (ret == 1) {
205					cp2 = X509_NAME_oneline(
206					    X509_get_subject_name(slp->cert),
207					    NULL, 0);
208					cp = X509_NAME_oneline(
209					    X509_get_issuer_name(slp->cert),
210					    NULL, 0);
211					if (rpctls_debug_level == 0)
212						syslog(LOG_INFO | LOG_DAEMON,
213						    "rpctls_daemon: Certificate"
214						    " Revoked "
215						    "issuerName=%s "
216						    "subjectName=%s: "
217						    "TCP connection closed",
218						    cp, cp2);
219					else
220						fprintf(stderr,
221						    "rpctls_daemon: Certificate"
222						    " Revoked "
223						    "issuerName=%s "
224						    "subjectName=%s: "
225						    "TCP connection closed",
226						    cp, cp2);
227					shutdown(slp->s, SHUT_WR);
228					slp->shutoff = true;
229				}
230			}
231		}
232		X509_CRL_free(crl);
233	}
234	BIO_free(infile);
235}
236
237void
238rpctls_verbose_out(const char *fmt, ...)
239{
240	va_list ap;
241
242	if (rpctls_verbose) {
243		va_start(ap, fmt);
244		if (rpctls_debug_level == 0)
245			vsyslog(LOG_INFO | LOG_DAEMON, fmt, ap);
246		else
247			vfprintf(stderr, fmt, ap);
248		va_end(ap);
249	}
250}
251
252/*
253 * Check a IP address against any host address in the
254 * certificate.  Basically getnameinfo(3) and
255 * X509_check_host().
256 */
257int
258rpctls_checkhost(struct sockaddr *sad, X509 *cert, unsigned int wildcard)
259{
260	char hostnam[NI_MAXHOST];
261	int ret;
262
263	if (getnameinfo((const struct sockaddr *)sad,
264	    sad->sa_len, hostnam, sizeof(hostnam),
265	    NULL, 0, NI_NAMEREQD) != 0)
266		return (0);
267	rpctls_verbose_out("rpctls_checkhost: DNS %s\n",
268	    hostnam);
269	ret = X509_check_host(cert, hostnam, strlen(hostnam),
270	    wildcard, NULL);
271	return (ret);
272}
273
274/*
275 * Get the peer's IP address.
276 */
277int
278rpctls_gethost(int s, struct sockaddr *sad, char *hostip, size_t hostlen)
279{
280	socklen_t slen;
281	int ret;
282
283	slen = sizeof(struct sockaddr_storage);
284	if (getpeername(s, sad, &slen) < 0)
285		return (0);
286	ret = 0;
287	if (getnameinfo((const struct sockaddr *)sad,
288	    sad->sa_len, hostip, hostlen,
289	    NULL, 0, NI_NUMERICHOST) == 0) {
290		rpctls_verbose_out("rpctls_gethost: %s\n",
291		    hostip);
292		ret = 1;
293	}
294	return (ret);
295}
296