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