Merge pull request #4621 from alexanderkyte/strdup_env
[mono.git] / mono / utils / mono-rand.c
1 /**
2  * \file
3  *
4  * Authors:
5  *      Mark Crichton (crichton@gimp.org)
6  *      Patrik Torstensson (p@rxc.se)
7  *      Sebastien Pouliot (sebastien@ximian.com)
8  *      Ludovic Henry (ludovic.henry@xamarin.com)
9  *
10  * Copyright 2001-2003 Ximian, Inc (http://www.ximian.com)
11  * Copyright 2004-2009 Novell, Inc (http://www.novell.com)
12  * Copyright 2001 Xamarin, Inc (http://www.novell.com)
13  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
14  */
15
16 #include <glib.h>
17 #include <config.h>
18
19 #include "atomic.h"
20 #include "mono-error.h"
21 #include "mono-error-internals.h"
22 #include "mono-rand.h"
23 #include "mono-threads.h"
24 #include "metadata/exception.h"
25 #include "metadata/object.h"
26
27 #ifdef HOST_WIN32
28 // Windows specific implementation in mono-rand-windows.c
29 #elif defined (HAVE_SYS_UN_H) && !defined(__native_client__)
30
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <sys/socket.h>
35 #include <sys/types.h>
36 #include <sys/un.h>
37
38 #ifndef NAME_DEV_URANDOM
39 #define NAME_DEV_URANDOM "/dev/urandom"
40 #endif
41
42 static gboolean use_egd = FALSE;
43 static gint file = -1;
44
45 static void
46 get_entropy_from_egd (const char *path, guchar *buffer, int buffer_size, MonoError *error)
47 {
48         struct sockaddr_un egd_addr;
49         gint socket_fd;
50         gint ret;
51         guint offset = 0;
52         int err = 0;
53
54         error_init (error);
55         
56         socket_fd = socket (PF_UNIX, SOCK_STREAM, 0);
57         if (socket_fd < 0) {
58                 ret = -1;
59                 err = errno;
60         } else {
61                 egd_addr.sun_family = AF_UNIX;
62                 memcpy (egd_addr.sun_path, path, sizeof (egd_addr.sun_path) - 1);
63                 egd_addr.sun_path [sizeof (egd_addr.sun_path) - 1] = '\0';
64                 ret = connect (socket_fd, (struct sockaddr*) &egd_addr, sizeof (egd_addr));
65                 err = errno;
66         }
67         if (ret == -1) {
68                 if (socket_fd >= 0)
69                         close (socket_fd);
70                 g_warning ("Entropy problem! Can't create or connect to egd socket %s", path);
71                 mono_error_set_execution_engine (error, "Failed to open egd socket %s: %s", path, strerror (err));
72                 return;
73         }
74
75         while (buffer_size > 0) {
76                 guchar request [2];
77                 gint count = 0;
78
79                 /* block until daemon can return enough entropy */
80                 request [0] = 2;
81                 request [1] = buffer_size < 255 ? buffer_size : 255;
82                 while (count < 2) {
83                         int sent = write (socket_fd, request + count, 2 - count);
84                         err = errno;
85                         if (sent >= 0) {
86                                 count += sent;
87                         } else if (err == EINTR) {
88                                 continue;
89                         } else {
90                                 close (socket_fd);
91                                 g_warning ("Send egd request failed %d", err);
92                                 mono_error_set_execution_engine (error, "Failed to send request to egd socket: %s", strerror (err));
93                                 return;
94                         }
95                 }
96
97                 count = 0;
98                 while (count != request [1]) {
99                         int received;
100                         received = read (socket_fd, buffer + offset, request [1] - count);
101                         err = errno;
102                         if (received > 0) {
103                                 count += received;
104                                 offset += received;
105                         } else if (received < 0 && err == EINTR) {
106                                 continue;
107                         } else {
108                                 close (socket_fd);
109                                 g_warning ("Receive egd request failed %d", err);
110                                 mono_error_set_execution_engine (error, "Failed to get response from egd socket: %s", strerror(err));
111                                 return;
112                         }
113                 }
114
115                 buffer_size -= request [1];
116         }
117
118         close (socket_fd);
119 }
120
121 gboolean
122 mono_rand_open (void)
123 {
124         static gint32 status = 0;
125         if (status != 0 || InterlockedCompareExchange (&status, 1, 0) != 0) {
126                 while (status != 2)
127                         mono_thread_info_yield ();
128                 return TRUE;
129         }
130
131 #ifdef NAME_DEV_URANDOM
132         file = open (NAME_DEV_URANDOM, O_RDONLY);
133 #endif
134 #ifdef NAME_DEV_RANDOM
135         if (file < 0)
136                 file = open (NAME_DEV_RANDOM, O_RDONLY);
137 #endif
138         if (file < 0)
139                 use_egd = g_hasenv ("MONO_EGD_SOCKET");
140
141         status = 2;
142
143         return TRUE;
144 }
145
146 gpointer
147 mono_rand_init (guchar *seed, gint seed_size)
148 {
149         // file < 0 is expected in the egd case
150         return (!use_egd && file < 0) ? NULL : GINT_TO_POINTER (file);
151 }
152
153 gboolean
154 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size, MonoError *error)
155 {
156         g_assert (handle);
157
158         error_init (error);
159
160         if (use_egd) {
161                 char *socket_path = g_getenv ("MONO_EGD_SOCKET");
162                 /* exception will be thrown in managed code */
163                 if (socket_path == NULL) {
164                         *handle = NULL;
165                         return FALSE;
166                 }
167                 get_entropy_from_egd (socket_path, buffer, buffer_size, error);
168                 g_free (socket_path);
169         } else {
170                 /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
171                 gint count = 0;
172                 gint err;
173
174                 do {
175                         err = read (file, buffer + count, buffer_size - count);
176                         if (err < 0) {
177                                 if (errno == EINTR)
178                                         continue;
179                                 g_warning("Entropy error! Error in read (%s).", strerror (errno));
180                                 /* exception will be thrown in managed code */
181                                 mono_error_set_execution_engine (error, "Entropy error! Error in read (%s).", strerror (errno));
182                                 return FALSE;
183                         }
184                         count += err;
185                 } while (count < buffer_size);
186         }
187         return TRUE;
188 }
189
190 void
191 mono_rand_close (gpointer provider)
192 {
193 }
194
195 #else
196
197 #include <stdlib.h>
198 #include <time.h>
199
200 gboolean
201 mono_rand_open (void)
202 {
203         static gint32 status = 0;
204         if (status != 0 || InterlockedCompareExchange (&status, 1, 0) != 0) {
205                 while (status != 2)
206                         mono_thread_info_yield ();
207                 return TRUE;
208         }
209
210         srand (time (NULL));
211
212         status = 2;
213
214         return TRUE;
215 }
216
217 gpointer
218 mono_rand_init (guchar *seed, gint seed_size)
219 {
220         return "srand"; // NULL will be interpreted as failure; return arbitrary nonzero pointer
221 }
222
223 gboolean
224 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size, MonoError *error)
225 {
226         gint count = 0;
227
228         error_init (error);
229         
230         do {
231                 if (buffer_size - count >= sizeof (gint32) && RAND_MAX >= 0xFFFFFFFF) {
232                         *(gint32*) buffer = rand();
233                         count += sizeof (gint32);
234                         buffer += sizeof (gint32) / sizeof (guchar);
235                 } else if (buffer_size - count >= sizeof (gint16) && RAND_MAX >= 0xFFFF) {
236                         *(gint16*) buffer = rand();
237                         count += sizeof (gint16);
238                         buffer += sizeof (gint16) / sizeof (guchar);
239                 } else if (buffer_size - count >= sizeof (gint8) && RAND_MAX >= 0xFF) {
240                         *(gint8*) buffer = rand();
241                         count += sizeof (gint8);
242                         buffer += sizeof (gint8) / sizeof (guchar);
243                 }
244         } while (count < buffer_size);
245
246         return TRUE;
247 }
248
249 void
250 mono_rand_close (gpointer provider)
251 {
252 }
253
254 #endif
255
256 /**
257  * mono_rand_try_get_uint32:
258  * \param handle A pointer to an RNG handle. Handle is set to NULL on failure.
259  * \param val A pointer to a 32-bit unsigned int, to which the result will be written.
260  * \param min Result will be greater than or equal to this value.
261  * \param max Result will be less than or equal to this value.
262  * Extracts one 32-bit unsigned int from an RNG handle.
263  * \returns FALSE on failure, TRUE on success.
264  */
265 gboolean
266 mono_rand_try_get_uint32 (gpointer *handle, guint32 *val, guint32 min, guint32 max, MonoError *error)
267 {
268         g_assert (val);
269         if (!mono_rand_try_get_bytes (handle, (guchar*) val, sizeof (guint32), error))
270                 return FALSE;
271
272         double randomDouble = ((gdouble) *val) / ( ((double)G_MAXUINT32) + 1 ); // Range is [0,1)
273         *val = (guint32) (randomDouble * (max - min + 1) + min);
274
275         g_assert (*val >= min);
276         g_assert (*val <= max);
277
278         return TRUE;
279 }