1/*	$NetBSD$	*/
2
3/*
4 * Copyright (C) 2004-2008  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
59typedef 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
74static unsigned int
75get_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
114static unsigned int
115get_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 */
251static void
252fillpool(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
391static int
392wait_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
445static void
446destroyfilesource(isc_entropyfilesource_t *source) {
447	(void)close(source->handle);
448}
449
450static void
451destroyusocketsource(isc_entropyusocketsource_t *source) {
452	close(source->handle);
453}
454
455/*
456 * Make a fd non-blocking
457 */
458static isc_result_t
459make_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
489isc_result_t
490isc_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		strncpy(sname.sun_path, fname, sizeof(sname.sun_path));
542		sname.sun_path[sizeof(sname.sun_path)-1] = '0';
543#ifdef ISC_PLATFORM_HAVESALEN
544#if !defined(SUN_LEN)
545#define SUN_LEN(su) \
546	(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
547#endif
548		sname.sun_len = SUN_LEN(&sname);
549#endif
550
551		if (connect(fd, (struct sockaddr *) &sname,
552			    sizeof(struct sockaddr_un)) < 0) {
553			if (errno != EINPROGRESS) {
554				ret = isc__errno2result(errno);
555				goto closefd;
556			}
557		} else
558			is_connected = ISC_TRUE;
559	}
560
561	source = isc_mem_get(ent->mctx, sizeof(isc_entropysource_t));
562	if (source == NULL) {
563		ret = ISC_R_NOMEMORY;
564		goto closefd;
565	}
566
567	/*
568	 * From here down, no failures can occur.
569	 */
570	source->magic = SOURCE_MAGIC;
571	source->ent = ent;
572	source->total = 0;
573	source->bad = ISC_FALSE;
574	memset(source->name, 0, sizeof(source->name));
575	ISC_LINK_INIT(source, link);
576	if (is_usocket) {
577		source->sources.usocket.handle = fd;
578		if (is_connected)
579			source->sources.usocket.status =
580					isc_usocketsource_connected;
581		else
582			source->sources.usocket.status =
583					isc_usocketsource_connecting;
584		source->sources.usocket.sz_to_recv = 0;
585		source->type = ENTROPY_SOURCETYPE_USOCKET;
586	} else {
587		source->sources.file.handle = fd;
588		source->type = ENTROPY_SOURCETYPE_FILE;
589	}
590
591	/*
592	 * Hook it into the entropy system.
593	 */
594	ISC_LIST_APPEND(ent->sources, source, link);
595	ent->nsources++;
596
597	UNLOCK(&ent->lock);
598	return (ISC_R_SUCCESS);
599
600 closefd:
601	(void)close(fd);
602
603 errout:
604	UNLOCK(&ent->lock);
605
606	return (ret);
607}
608