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