xref: /minix/external/bsd/bind/dist/lib/isc/unix/entropy.c (revision fb9c64b2)
1 /*	$NetBSD: entropy.c,v 1.5 2014/12/10 04:38:01 christos Exp $	*/
2 
3 /*
4  * Copyright (C) 2004-2008, 2012  Internet Systems Consortium, Inc. ("ISC")
5  * Copyright (C) 2000-2003  Internet Software Consortium.
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17  * PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 /* Id: entropy.c,v 1.82 2008/12/01 23:47:45 tbox Exp  */
21 
22 /* \file unix/entropy.c
23  * \brief
24  * This is the system dependent part of the ISC entropy API.
25  */
26 
27 #include <config.h>
28 
29 #include <sys/param.h>	/* Openserver 5.0.6A and FD_SETSIZE */
30 #include <sys/types.h>
31 #include <sys/time.h>
32 #include <sys/stat.h>
33 #include <sys/socket.h>
34 #include <sys/un.h>
35 
36 #ifdef HAVE_NANOSLEEP
37 #include <time.h>
38 #endif
39 #include <unistd.h>
40 
41 #include <isc/platform.h>
42 #include <isc/strerror.h>
43 
44 #ifdef ISC_PLATFORM_NEEDSYSSELECTH
45 #include <sys/select.h>
46 #endif
47 
48 #include "errno2result.h"
49 
50 /*%
51  * There is only one variable in the entropy data structures that is not
52  * system independent, but pulling the structure that uses it into this file
53  * ultimately means pulling several other independent structures here also to
54  * resolve their interdependencies.  Thus only the problem variable's type
55  * is defined here.
56  */
57 #define FILESOURCE_HANDLE_TYPE	int
58 
59 typedef struct {
60 	int	handle;
61 	enum	{
62 		isc_usocketsource_disconnected,
63 		isc_usocketsource_connecting,
64 		isc_usocketsource_connected,
65 		isc_usocketsource_ndesired,
66 		isc_usocketsource_wrote,
67 		isc_usocketsource_reading
68 	} status;
69 	size_t	sz_to_recv;
70 } isc_entropyusocketsource_t;
71 
72 #include "../entropy.c"
73 
74 static unsigned int
75 get_from_filesource(isc_entropysource_t *source, isc_uint32_t desired) {
76 	isc_entropy_t *ent = source->ent;
77 	unsigned char buf[128];
78 	int fd = source->sources.file.handle;
79 	ssize_t n, ndesired;
80 	unsigned int added;
81 
82 	if (source->bad)
83 		return (0);
84 
85 	desired = desired / 8 + (((desired & 0x07) > 0) ? 1 : 0);
86 
87 	added = 0;
88 	while (desired > 0) {
89 		ndesired = ISC_MIN(desired, sizeof(buf));
90 		n = read(fd, buf, ndesired);
91 		if (n < 0) {
92 			if (errno == EAGAIN || errno == EINTR)
93 				goto out;
94 			goto err;
95 		}
96 		if (n == 0)
97 			goto err;
98 
99 		entropypool_adddata(ent, buf, n, n * 8);
100 		added += n * 8;
101 		desired -= n;
102 	}
103 	goto out;
104 
105  err:
106 	(void)close(fd);
107 	source->sources.file.handle = -1;
108 	source->bad = ISC_TRUE;
109 
110  out:
111 	return (added);
112 }
113 
114 static unsigned int
115 get_from_usocketsource(isc_entropysource_t *source, isc_uint32_t desired) {
116 	isc_entropy_t *ent = source->ent;
117 	unsigned char buf[128];
118 	int fd = source->sources.usocket.handle;
119 	ssize_t n = 0, ndesired;
120 	unsigned int added;
121 	size_t sz_to_recv = source->sources.usocket.sz_to_recv;
122 
123 	if (source->bad)
124 		return (0);
125 
126 	desired = desired / 8 + (((desired & 0x07) > 0) ? 1 : 0);
127 
128 	added = 0;
129 	while (desired > 0) {
130 		ndesired = ISC_MIN(desired, sizeof(buf));
131  eagain_loop:
132 
133 		switch ( source->sources.usocket.status ) {
134 		case isc_usocketsource_ndesired:
135 			buf[0] = ndesired;
136 			if ((n = sendto(fd, buf, 1, 0, NULL, 0)) < 0) {
137 				if (errno == EWOULDBLOCK || errno == EINTR ||
138 				    errno == ECONNRESET)
139 					goto out;
140 				goto err;
141 			}
142 			INSIST(n == 1);
143 			source->sources.usocket.status =
144 						isc_usocketsource_wrote;
145 			goto eagain_loop;
146 
147 		case isc_usocketsource_connecting:
148 		case isc_usocketsource_connected:
149 			buf[0] = 1;
150 			buf[1] = ndesired;
151 			if ((n = sendto(fd, buf, 2, 0, NULL, 0)) < 0) {
152 				if (errno == EWOULDBLOCK || errno == EINTR ||
153 				    errno == ECONNRESET)
154 					goto out;
155 				goto err;
156 			}
157 			if (n == 1) {
158 				source->sources.usocket.status =
159 					isc_usocketsource_ndesired;
160 				goto eagain_loop;
161 			}
162 			INSIST(n == 2);
163 			source->sources.usocket.status =
164 						isc_usocketsource_wrote;
165 			/*FALLTHROUGH*/
166 
167 		case isc_usocketsource_wrote:
168 			if (recvfrom(fd, buf, 1, 0, NULL, NULL) != 1) {
169 				if (errno == EAGAIN) {
170 					/*
171 					 * The problem of EAGAIN (try again
172 					 * later) is a major issue on HP-UX.
173 					 * Solaris actually tries the recvfrom
174 					 * call again, while HP-UX just dies.
175 					 * This code is an attempt to let the
176 					 * entropy pool fill back up (at least
177 					 * that's what I think the problem is.)
178 					 * We go to eagain_loop because if we
179 					 * just "break", then the "desired"
180 					 * amount gets borked.
181 					 */
182 #ifdef HAVE_NANOSLEEP
183 					struct timespec ts;
184 
185 					ts.tv_sec = 0;
186 					ts.tv_nsec = 1000000;
187 					nanosleep(&ts, NULL);
188 #else
189 					usleep(1000);
190 #endif
191 					goto eagain_loop;
192 				}
193 				if (errno == EWOULDBLOCK || errno == EINTR)
194 					goto out;
195 				goto err;
196 			}
197 			source->sources.usocket.status =
198 					isc_usocketsource_reading;
199 			sz_to_recv = buf[0];
200 			source->sources.usocket.sz_to_recv = sz_to_recv;
201 			if (sz_to_recv > sizeof(buf))
202 				goto err;
203 			/*FALLTHROUGH*/
204 
205 		case isc_usocketsource_reading:
206 			if (sz_to_recv != 0U) {
207 				n = recv(fd, buf, sz_to_recv, 0);
208 				if (n < 0) {
209 					if (errno == EWOULDBLOCK ||
210 					    errno == EINTR)
211 						goto out;
212 					goto err;
213 				}
214 			} else
215 				n = 0;
216 			break;
217 
218 		default:
219 			goto err;
220 		}
221 
222 		if ((size_t)n != sz_to_recv)
223 			source->sources.usocket.sz_to_recv -= n;
224 		else
225 			source->sources.usocket.status =
226 				isc_usocketsource_connected;
227 
228 		if (n == 0)
229 			goto out;
230 
231 		entropypool_adddata(ent, buf, n, n * 8);
232 		added += n * 8;
233 		desired -= n;
234 	}
235 	goto out;
236 
237  err:
238 	close(fd);
239 	source->bad = ISC_TRUE;
240 	source->sources.usocket.status = isc_usocketsource_disconnected;
241 	source->sources.usocket.handle = -1;
242 
243  out:
244 	return (added);
245 }
246 
247 /*
248  * Poll each source, trying to get data from it to stuff into the entropy
249  * pool.
250  */
251 static void
252 fillpool(isc_entropy_t *ent, unsigned int desired, isc_boolean_t blocking) {
253 	unsigned int added;
254 	unsigned int remaining;
255 	unsigned int needed;
256 	unsigned int nsource;
257 	isc_entropysource_t *source;
258 
259 	REQUIRE(VALID_ENTROPY(ent));
260 
261 	needed = desired;
262 
263 	/*
264 	 * This logic is a little strange, so an explanation is in order.
265 	 *
266 	 * If needed is 0, it means we are being asked to "fill to whatever
267 	 * we think is best."  This means that if we have at least a
268 	 * partially full pool (say, > 1/4th of the pool) we probably don't
269 	 * need to add anything.
270 	 *
271 	 * Also, we will check to see if the "pseudo" count is too high.
272 	 * If it is, try to mix in better data.  Too high is currently
273 	 * defined as 1/4th of the pool.
274 	 *
275 	 * Next, if we are asked to add a specific bit of entropy, make
276 	 * certain that we will do so.  Clamp how much we try to add to
277 	 * (DIGEST_SIZE * 8 < needed < POOLBITS - entropy).
278 	 *
279 	 * Note that if we are in a blocking mode, we will only try to
280 	 * get as much data as we need, not as much as we might want
281 	 * to build up.
282 	 */
283 	if (needed == 0) {
284 		REQUIRE(!blocking);
285 
286 		if ((ent->pool.entropy >= RND_POOLBITS / 4)
287 		    && (ent->pool.pseudo <= RND_POOLBITS / 4))
288 			return;
289 
290 		needed = THRESHOLD_BITS * 4;
291 	} else {
292 		needed = ISC_MAX(needed, THRESHOLD_BITS);
293 		needed = ISC_MIN(needed, RND_POOLBITS);
294 	}
295 
296 	/*
297 	 * In any case, clamp how much we need to how much we can add.
298 	 */
299 	needed = ISC_MIN(needed, RND_POOLBITS - ent->pool.entropy);
300 
301 	/*
302 	 * But wait!  If we're not yet initialized, we need at least
303 	 *	THRESHOLD_BITS
304 	 * of randomness.
305 	 */
306 	if (ent->initialized < THRESHOLD_BITS)
307 		needed = ISC_MAX(needed, THRESHOLD_BITS - ent->initialized);
308 
309 	/*
310 	 * Poll each file source to see if we can read anything useful from
311 	 * it.  XXXMLG When where are multiple sources, we should keep a
312 	 * record of which one we last used so we can start from it (or the
313 	 * next one) to avoid letting some sources build up entropy while
314 	 * others are always drained.
315 	 */
316 
317 	added = 0;
318 	remaining = needed;
319 	if (ent->nextsource == NULL) {
320 		ent->nextsource = ISC_LIST_HEAD(ent->sources);
321 		if (ent->nextsource == NULL)
322 			return;
323 	}
324 	source = ent->nextsource;
325  again_file:
326 	for (nsource = 0; nsource < ent->nsources; nsource++) {
327 		unsigned int got;
328 
329 		if (remaining == 0)
330 			break;
331 
332 		got = 0;
333 
334 		switch ( source->type ) {
335 		case ENTROPY_SOURCETYPE_FILE:
336 			got = get_from_filesource(source, remaining);
337 			break;
338 
339 		case ENTROPY_SOURCETYPE_USOCKET:
340 			got = get_from_usocketsource(source, remaining);
341 			break;
342 		}
343 
344 		added += got;
345 
346 		remaining -= ISC_MIN(remaining, got);
347 
348 		source = ISC_LIST_NEXT(source, link);
349 		if (source == NULL)
350 			source = ISC_LIST_HEAD(ent->sources);
351 	}
352 	ent->nextsource = source;
353 
354 	if (blocking && remaining != 0) {
355 		int fds;
356 
357 		fds = wait_for_sources(ent);
358 		if (fds > 0)
359 			goto again_file;
360 	}
361 
362 	/*
363 	 * Here, if there are bits remaining to be had and we can block,
364 	 * check to see if we have a callback source.  If so, call them.
365 	 */
366 	source = ISC_LIST_HEAD(ent->sources);
367 	while ((remaining != 0) && (source != NULL)) {
368 		unsigned int got;
369 
370 		got = 0;
371 
372 		if (source->type == ENTROPY_SOURCETYPE_CALLBACK)
373 			got = get_from_callback(source, remaining, blocking);
374 
375 		added += got;
376 		remaining -= ISC_MIN(remaining, got);
377 
378 		if (added >= needed)
379 			break;
380 
381 		source = ISC_LIST_NEXT(source, link);
382 	}
383 
384 	/*
385 	 * Mark as initialized if we've added enough data.
386 	 */
387 	if (ent->initialized < THRESHOLD_BITS)
388 		ent->initialized += added;
389 }
390 
391 static int
392 wait_for_sources(isc_entropy_t *ent) {
393 	isc_entropysource_t *source;
394 	int maxfd, fd;
395 	int cc;
396 	fd_set reads;
397 	fd_set writes;
398 
399 	maxfd = -1;
400 	FD_ZERO(&reads);
401 	FD_ZERO(&writes);
402 
403 	source = ISC_LIST_HEAD(ent->sources);
404 	while (source != NULL) {
405 		if (source->type == ENTROPY_SOURCETYPE_FILE) {
406 			fd = source->sources.file.handle;
407 			if (fd >= 0) {
408 				maxfd = ISC_MAX(maxfd, fd);
409 				FD_SET(fd, &reads);
410 			}
411 		}
412 		if (source->type == ENTROPY_SOURCETYPE_USOCKET) {
413 			fd = source->sources.usocket.handle;
414 			if (fd >= 0) {
415 				switch (source->sources.usocket.status) {
416 				case isc_usocketsource_disconnected:
417 					break;
418 				case isc_usocketsource_connecting:
419 				case isc_usocketsource_connected:
420 				case isc_usocketsource_ndesired:
421 					maxfd = ISC_MAX(maxfd, fd);
422 					FD_SET(fd, &writes);
423 					break;
424 				case isc_usocketsource_wrote:
425 				case isc_usocketsource_reading:
426 					maxfd = ISC_MAX(maxfd, fd);
427 					FD_SET(fd, &reads);
428 					break;
429 				}
430 			}
431 		}
432 		source = ISC_LIST_NEXT(source, link);
433 	}
434 
435 	if (maxfd < 0)
436 		return (-1);
437 
438 	cc = select(maxfd + 1, &reads, &writes, NULL, NULL);
439 	if (cc < 0)
440 		return (-1);
441 
442 	return (cc);
443 }
444 
445 static void
446 destroyfilesource(isc_entropyfilesource_t *source) {
447 	(void)close(source->handle);
448 }
449 
450 static void
451 destroyusocketsource(isc_entropyusocketsource_t *source) {
452 	close(source->handle);
453 }
454 
455 /*
456  * Make a fd non-blocking
457  */
458 static isc_result_t
459 make_nonblock(int fd) {
460 	int ret;
461 	int flags;
462 	char strbuf[ISC_STRERRORSIZE];
463 #ifdef USE_FIONBIO_IOCTL
464 	int on = 1;
465 
466 	ret = ioctl(fd, FIONBIO, (char *)&on);
467 #else
468 	flags = fcntl(fd, F_GETFL, 0);
469 	flags |= PORT_NONBLOCK;
470 	ret = fcntl(fd, F_SETFL, flags);
471 #endif
472 
473 	if (ret == -1) {
474 		isc__strerror(errno, strbuf, sizeof(strbuf));
475 		UNEXPECTED_ERROR(__FILE__, __LINE__,
476 #ifdef USE_FIONBIO_IOCTL
477 				 "ioctl(%d, FIONBIO, &on): %s", fd,
478 #else
479 				 "fcntl(%d, F_SETFL, %d): %s", fd, flags,
480 #endif
481 				 strbuf);
482 
483 		return (ISC_R_UNEXPECTED);
484 	}
485 
486 	return (ISC_R_SUCCESS);
487 }
488 
489 isc_result_t
490 isc_entropy_createfilesource(isc_entropy_t *ent, const char *fname) {
491 	int fd;
492 	struct stat _stat;
493 	isc_boolean_t is_usocket = ISC_FALSE;
494 	isc_boolean_t is_connected = ISC_FALSE;
495 	isc_result_t ret;
496 	isc_entropysource_t *source;
497 
498 	REQUIRE(VALID_ENTROPY(ent));
499 	REQUIRE(fname != NULL);
500 
501 	LOCK(&ent->lock);
502 
503 	if (stat(fname, &_stat) < 0) {
504 		ret = isc__errno2result(errno);
505 		goto errout;
506 	}
507 	/*
508 	 * Solaris 2.5.1 does not have support for sockets (S_IFSOCK),
509 	 * but it does return type S_IFIFO (the OS believes that
510 	 * the socket is a fifo).  This may be an issue if we tell
511 	 * the program to look at an actual FIFO as its source of
512 	 * entropy.
513 	 */
514 #if defined(S_ISSOCK)
515 	if (S_ISSOCK(_stat.st_mode))
516 		is_usocket = ISC_TRUE;
517 #endif
518 #if defined(S_ISFIFO) && defined(sun)
519 	if (S_ISFIFO(_stat.st_mode))
520 		is_usocket = ISC_TRUE;
521 #endif
522 	if (is_usocket)
523 		fd = socket(PF_UNIX, SOCK_STREAM, 0);
524 	else
525 		fd = open(fname, O_RDONLY | PORT_NONBLOCK, 0);
526 
527 	if (fd < 0) {
528 		ret = isc__errno2result(errno);
529 		goto errout;
530 	}
531 
532 	ret = make_nonblock(fd);
533 	if (ret != ISC_R_SUCCESS)
534 		goto closefd;
535 
536 	if (is_usocket) {
537 		struct sockaddr_un sname;
538 
539 		memset(&sname, 0, sizeof(sname));
540 		sname.sun_family = AF_UNIX;
541 		strlcpy(sname.sun_path, fname, sizeof(sname.sun_path));
542 #ifdef ISC_PLATFORM_HAVESALEN
543 #if !defined(SUN_LEN)
544 #define SUN_LEN(su) \
545 	(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
546 #endif
547 		sname.sun_len = SUN_LEN(&sname);
548 #endif
549 
550 		if (connect(fd, (struct sockaddr *) &sname,
551 			    sizeof(struct sockaddr_un)) < 0) {
552 			if (errno != EINPROGRESS) {
553 				ret = isc__errno2result(errno);
554 				goto closefd;
555 			}
556 		} else
557 			is_connected = ISC_TRUE;
558 	}
559 
560 	source = isc_mem_get(ent->mctx, sizeof(isc_entropysource_t));
561 	if (source == NULL) {
562 		ret = ISC_R_NOMEMORY;
563 		goto closefd;
564 	}
565 
566 	/*
567 	 * From here down, no failures can occur.
568 	 */
569 	source->magic = SOURCE_MAGIC;
570 	source->ent = ent;
571 	source->total = 0;
572 	source->bad = ISC_FALSE;
573 	memset(source->name, 0, sizeof(source->name));
574 	ISC_LINK_INIT(source, link);
575 	if (is_usocket) {
576 		source->sources.usocket.handle = fd;
577 		if (is_connected)
578 			source->sources.usocket.status =
579 					isc_usocketsource_connected;
580 		else
581 			source->sources.usocket.status =
582 					isc_usocketsource_connecting;
583 		source->sources.usocket.sz_to_recv = 0;
584 		source->type = ENTROPY_SOURCETYPE_USOCKET;
585 	} else {
586 		source->sources.file.handle = fd;
587 		source->type = ENTROPY_SOURCETYPE_FILE;
588 	}
589 
590 	/*
591 	 * Hook it into the entropy system.
592 	 */
593 	ISC_LIST_APPEND(ent->sources, source, link);
594 	ent->nsources++;
595 
596 	UNLOCK(&ent->lock);
597 	return (ISC_R_SUCCESS);
598 
599  closefd:
600 	(void)close(fd);
601 
602  errout:
603 	UNLOCK(&ent->lock);
604 
605 	return (ret);
606 }
607