Merge pull request #1222 from LogosBible/uri-trycreate
[mono.git] / mono / utils / mono-rand.c
1 /*
2  * mono-rand.c: 
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  */
14
15
16 #include <glib.h>
17 #include <config.h>
18
19 #include "mono-rand.h"
20 #include "metadata/exception.h"
21 #include "metadata/object.h"
22
23 #ifdef HOST_WIN32
24
25 #include <windows.h>
26 #include <wincrypt.h>
27
28 #ifndef PROV_INTEL_SEC
29 #define PROV_INTEL_SEC          22
30 #endif
31 #ifndef CRYPT_VERIFY_CONTEXT
32 #define CRYPT_VERIFY_CONTEXT    0xF0000000
33 #endif
34
35 gboolean
36 mono_rand_open (void)
37 {
38         /* FALSE == Local (instance) handle for randomness */
39         return FALSE;
40 }
41
42 gpointer
43 mono_rand_init (guchar *seed, gint seed_size)
44 {
45         HCRYPTPROV provider = 0;
46
47         /* There is no need to create a container for just random data,
48          * so we can use CRYPT_VERIFY_CONTEXT (one call) see: 
49          * http://blogs.msdn.com/dangriff/archive/2003/11/19/51709.aspx */
50
51         /* We first try to use the Intel PIII RNG if drivers are present */
52         if (!CryptAcquireContext (&provider, NULL, NULL, PROV_INTEL_SEC, CRYPT_VERIFY_CONTEXT)) {
53                 /* not a PIII or no drivers available, use default RSA CSP */
54                 if (!CryptAcquireContext (&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFY_CONTEXT)) {
55                         /* exception will be thrown in managed code */
56                         provider = 0;
57                 }
58         }
59
60         /* seed the CSP with the supplied buffer (if present) */
61         if (provider != 0 && seed) {
62                 /* the call we replace the seed with random - this isn't what is
63                  * expected from the class library user */
64                 guchar *data = g_malloc (seed_size);
65                 if (data) {
66                         memcpy (data, seed, seed_size);
67                         /* add seeding material to the RNG */
68                         CryptGenRandom (provider, seed_size, data);
69                         /* zeroize and free */
70                         memset (data, 0, seed_size);
71                         g_free (data);
72                 }
73         }
74
75         return (gpointer) provider;
76 }
77
78 gboolean
79 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
80 {
81         HCRYPTPROV provider;
82
83         g_assert (handle);
84         provider = (HCRYPTPROV) *handle;
85
86         if (!CryptGenRandom (provider, buffer_size, buffer)) {
87                 CryptReleaseContext (provider, 0);
88                 /* we may have lost our context with CryptoAPI, but all hope isn't lost yet! */
89                 provider = (HCRYPTPROV) mono_rand_init (NULL, 0);
90                 if (!CryptGenRandom (provider, buffer_size, buffer)) {
91                         /* exception will be thrown in managed code */
92                         CryptReleaseContext (provider, 0);
93                         *handle = 0;
94                         return FALSE;
95                 }
96         }
97         return TRUE;
98 }
99
100 void
101 mono_rand_close (gpointer handle)
102 {
103         CryptReleaseContext ((HCRYPTPROV) handle, 0);
104 }
105
106 #elif defined (HAVE_SYS_UN_H) && !defined(__native_client__)
107
108 #include <errno.h>
109 #include <fcntl.h>
110 #include <unistd.h>
111 #include <sys/socket.h>
112 #include <sys/types.h>
113 #include <sys/un.h>
114
115 #ifndef NAME_DEV_URANDOM
116 #define NAME_DEV_URANDOM "/dev/urandom"
117 #endif
118
119 static gboolean use_egd = FALSE;
120 static gint file = -1;
121
122 static void
123 get_entropy_from_egd (const char *path, guchar *buffer, int buffer_size)
124 {
125         struct sockaddr_un egd_addr;
126         gint file;
127         gint ret;
128         guint offset = 0;
129
130         file = socket (PF_UNIX, SOCK_STREAM, 0);
131         if (file < 0) {
132                 ret = -1;
133         } else {
134                 egd_addr.sun_family = AF_UNIX;
135                 strncpy (egd_addr.sun_path, path, sizeof (egd_addr.sun_path) - 1);
136                 egd_addr.sun_path [sizeof (egd_addr.sun_path) - 1] = '\0';
137                 ret = connect (file, (struct sockaddr*) &egd_addr, sizeof (egd_addr));
138         }
139         if (ret == -1) {
140                 if (file >= 0)
141                         close (file);
142                 g_warning ("Entropy problem! Can't create or connect to egd socket %s", path);
143                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to open egd socket"));
144         }
145
146         while (buffer_size > 0) {
147                 guchar request [2];
148                 gint count = 0;
149
150                 /* block until daemon can return enough entropy */
151                 request [0] = 2;
152                 request [1] = buffer_size < 255 ? buffer_size : 255;
153                 while (count < 2) {
154                         int sent = write (file, request + count, 2 - count);
155                         if (sent >= 0) {
156                                 count += sent;
157                         } else if (errno == EINTR) {
158                                 continue;
159                         } else {
160                                 close (file);
161                                 g_warning ("Send egd request failed %d", errno);
162                                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to send request to egd socket"));
163                         }
164                 }
165
166                 count = 0;
167                 while (count != request [1]) {
168                         int received;
169                         received = read (file, buffer + offset, request [1] - count);
170                         if (received > 0) {
171                                 count += received;
172                                 offset += received;
173                         } else if (received < 0 && errno == EINTR) {
174                                 continue;
175                         } else {
176                                 close (file);
177                                 g_warning ("Receive egd request failed %d", errno);
178                                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to get response from egd socket"));
179                         }
180                 }
181
182                 buffer_size -= request [1];
183         }
184
185         close (file);
186 }
187
188 gboolean
189 mono_rand_open (void)
190 {
191         if (use_egd || (file >= 0))
192                 return TRUE;
193
194 #ifdef NAME_DEV_URANDOM
195         file = open (NAME_DEV_URANDOM, O_RDONLY);
196 #endif
197 #ifdef NAME_DEV_RANDOM
198         if (file < 0)
199                 file = open (NAME_DEV_RANDOM, O_RDONLY);
200 #endif
201         if (file < 0)
202                 use_egd = g_getenv("MONO_EGD_SOCKET") != NULL;
203
204         return TRUE;
205 }
206
207 gpointer
208 mono_rand_init (guchar *seed, gint seed_size)
209 {
210         /* if required exception will be thrown in managed code */
211         return (!use_egd && file < 0) ? NULL : GINT_TO_POINTER (file);
212 }
213
214 gboolean
215 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
216 {
217         g_assert (handle);
218
219         if (use_egd) {
220                 const char *socket_path = g_getenv ("MONO_EGD_SOCKET");
221                 /* exception will be thrown in managed code */
222                 if (socket_path == NULL) {
223                         *handle = NULL;
224                         return FALSE;
225                 }
226                 get_entropy_from_egd (socket_path, buffer, buffer_size);
227         } else {
228                 /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
229                 gint count = 0;
230                 gint err;
231
232                 do {
233                         err = read (file, buffer + count, buffer_size - count);
234                         if (err < 0) {
235                                 if (errno == EINTR)
236                                         continue;
237                                 g_warning("Entropy error! Error in read (%s).", strerror (errno));
238                                 /* exception will be thrown in managed code */
239                                 return FALSE;
240                         }
241                         count += err;
242                 } while (count < buffer_size);
243         }
244         return TRUE;
245 }
246
247 void
248 mono_rand_close (gpointer provider)
249 {
250 }
251
252 #else
253
254 #include <stdlib.h>
255 #include <time.h>
256
257 gboolean
258 mono_rand_open (void)
259 {
260         srand (time (NULL));
261         return TRUE;
262 }
263
264 gpointer
265 mono_rand_init (guchar *seed, gint seed_size)
266 {
267         return NULL;    
268 }
269
270 gboolean
271 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
272 {
273         gint count = 0;
274
275         do {
276                 if (buffer_size - count >= sizeof (gint32) && RAND_MAX >= 0xFFFFFFFF) {
277                         *(gint32*) buffer = rand();
278                         count += sizeof (gint32);
279                         buffer += sizeof (gint32) / sizeof (guchar);
280                 } else if (buffer_size - count >= sizeof (gint16) && RAND_MAX >= 0xFFFF) {
281                         *(gint16*) buffer = rand();
282                         count += sizeof (gint16);
283                         buffer += sizeof (gint16) / sizeof (guchar);
284                 } else if (buffer_size - count >= sizeof (gint8) && RAND_MAX >= 0xFF) {
285                         *(gint8*) buffer = rand();
286                         count += sizeof (gint8);
287                         buffer += sizeof (gint8) / sizeof (guchar);
288                 }
289         } while (count < buffer_size);
290
291         return TRUE;
292 }
293
294 void
295 mono_rand_close (gpointer provider)
296 {
297 }
298
299 #endif
300
301 gboolean
302 mono_rand_try_get_uint32 (gpointer *handle, guint32 *val, guint32 min, guint32 max)
303 {
304         g_assert (val);
305         if (!mono_rand_try_get_bytes (handle, (guchar*) val, sizeof (guint32)))
306                 return FALSE;
307
308         *val = (guint32) (((gdouble) *val) / G_MAXUINT32 * (max - min) + min);
309         g_assert (*val >= min);
310         g_assert (*val <= max);
311
312         return TRUE;
313 }