/src/resiprocate/rutil/Random.cxx
Line | Count | Source |
1 | | #if defined(HAVE_CONFIG_H) |
2 | | #include "config.h" |
3 | | #endif |
4 | | |
5 | | #include "rutil/ResipAssert.h" |
6 | | #include <stdlib.h> |
7 | | |
8 | | #ifdef WIN32 |
9 | | #include "rutil/Socket.hxx" |
10 | | #include "rutil/DataStream.hxx" |
11 | | #include "rutil/Data.hxx" |
12 | | #else |
13 | | #include <unistd.h> |
14 | | #include <sys/types.h> |
15 | | #include <sys/stat.h> |
16 | | #include <fcntl.h> |
17 | | #endif |
18 | | |
19 | | #include "rutil/Random.hxx" |
20 | | #include "rutil/Timer.hxx" |
21 | | #include "rutil/Mutex.hxx" |
22 | | #include "rutil/Lock.hxx" |
23 | | #include "rutil/Logger.hxx" |
24 | | |
25 | | |
26 | | #ifdef USE_SSL |
27 | | #ifdef WIN32 |
28 | | //hack for name collision of OCSP_RESPONSE and wincrypt.h in latest openssl release 0.9.8h |
29 | | //http://www.google.com/search?q=OCSP%5fRESPONSE+wincrypt%2eh |
30 | | //continue to watch this issue for a real fix. |
31 | | #undef OCSP_RESPONSE |
32 | | #endif |
33 | | #include "rutil/ssl/OpenSSLInit.hxx" |
34 | | # define USE_OPENSSL 1 |
35 | | #else |
36 | | # define USE_OPENSSL 0 |
37 | | #endif |
38 | | |
39 | | #if ( USE_OPENSSL == 1 ) |
40 | | # include <openssl/opensslv.h> |
41 | | #if !defined(LIBRESSL_VERSION_NUMBER) |
42 | | # include <openssl/e_os2.h> |
43 | | #endif |
44 | | # include <openssl/rand.h> |
45 | | # include <openssl/err.h> |
46 | | #endif |
47 | | |
48 | | using namespace resip; |
49 | | #define RESIPROCATE_SUBSYSTEM Subsystem::SIP |
50 | | |
51 | | Mutex Random::mMutex; |
52 | | bool Random::mIsInitialized = false; |
53 | | |
54 | | #ifdef WIN32 |
55 | | Random::Initializer Random::mInitializer; |
56 | | #ifdef RESIP_RANDOM_WIN32_RTL |
57 | | BOOLEAN (APIENTRY *Random::RtlGenRandom)(void*, ULONG) = 0; |
58 | | #endif |
59 | | #endif //WIN32 |
60 | | |
61 | | #ifdef RESIP_RANDOM_THREAD_MUTEX |
62 | | struct random_data* Random::sRandomState = 0; |
63 | | #endif |
64 | | |
65 | | #ifdef RESIP_RANDOM_THREAD_LOCAL |
66 | | ThreadIf::TlsKey Random::sRandomStateKey = 0; |
67 | | namespace |
68 | | { |
69 | | // Process-wide entropy read from /dev/urandom in initialize(), XORed into |
70 | | // each thread's PRNG seed so per-thread state is not derived solely from |
71 | | // time+pid (CWE-338). |
72 | | unsigned int sThreadSeedBase = 0; |
73 | | } |
74 | | #endif |
75 | | |
76 | | #define RANDOM_STATE_SIZE 128 |
77 | | |
78 | | const char* |
79 | | Random::getImplName() |
80 | 0 | { |
81 | | #ifdef WIN32 |
82 | | #if defined(RESIP_RANDOM_WIN32_RTL) |
83 | | return "win32_rtl"; |
84 | | #else |
85 | | return "win32_rand"; |
86 | | #endif |
87 | | #else // WIN32 |
88 | | #if defined(RESIP_RANDOM_THREAD_LOCAL) |
89 | | return "posix_thread_local"; |
90 | | #elif defined(RESIP_RANDOM_THREAD_MUTEX) |
91 | | return "posix_thread_mutex"; |
92 | | #else |
93 | 0 | return "posix_random"; |
94 | 0 | #endif |
95 | 0 | #endif // not WIN32 |
96 | 0 | } |
97 | | |
98 | | /** |
99 | | Key goal is to make sure that each thread has distinct seed. |
100 | | **/ |
101 | | unsigned |
102 | | Random::getSimpleSeed() |
103 | 1 | { |
104 | | // !cj! need to find a better way - use pentium random commands? |
105 | 1 | Data buffer; |
106 | 1 | { |
107 | 1 | DataStream strm(buffer); |
108 | | #ifdef WIN32 |
109 | | strm << GetTickCount() << ":"; |
110 | | strm << GetCurrentProcessId() << ":"; |
111 | | strm << GetCurrentThreadId(); |
112 | | #else |
113 | | // .kw. previously just used the lower 32bits of getTimeMs() |
114 | 1 | strm << ResipClock::getTimeMicroSec() << ":"; |
115 | 1 | strm << getpid(); |
116 | | #if defined(RESIP_RANDOM_THREAD_LOCAL) |
117 | | strm << ":" << ThreadIf::selfId(); |
118 | | #endif |
119 | 1 | #endif |
120 | 1 | } |
121 | 1 | return (unsigned int)buffer.hash(); |
122 | 1 | } |
123 | | |
124 | | void |
125 | | Random::initialize() |
126 | 19.9k | { |
127 | | #ifdef WIN32 |
128 | | //#if defined(USE_SSL) |
129 | | #if 0 //!dcm! - this shouldn't be per thread for win32, and this is slow. Going |
130 | | //to re-work openssl initialization |
131 | | if ( !Random::mIsInitialized) |
132 | | { |
133 | | Lock lock(mMutex); |
134 | | if (!Random::mIsInitialized) |
135 | | { |
136 | | mIsInitialized = true; |
137 | | RAND_screen (); |
138 | | } |
139 | | } |
140 | | #else |
141 | | if (!Random::mInitializer.isInitialized()) |
142 | | { |
143 | | Lock lock(mMutex); |
144 | | if (!Random::mInitializer.isInitialized()) |
145 | | { |
146 | | Random::mInitializer.setInitialized(); |
147 | | |
148 | | unsigned seed = getSimpleSeed(); |
149 | | srand(seed); |
150 | | |
151 | | #ifdef RESIP_RANDOM_WIN32_RTL |
152 | | // .jjg. from http://blogs.msdn.com/michael_howard/archive/2005/01/14/353379.aspx |
153 | | // srand(..) and rand() have proven to be insufficient sources of randomness, |
154 | | // leading to transaction id collisions in resip. |
155 | | // SystemFunction036 maps to RtlGenRandom, which is used by rand_s() (which is available |
156 | | // only with the VC 8.0 runtime or later) and is the Microsoft-recommended way of getting |
157 | | // a random number. This code allows that functionality to be accessed even from VC 7.1. |
158 | | // However, SystemFunction036 only exists in Windows XP and later, so we may need to fallback |
159 | | // to the old method using rand(). |
160 | | HMODULE hLib = GetModuleHandle("ADVAPI32.DLL"); |
161 | | if (hLib) |
162 | | { |
163 | | Random::RtlGenRandom = |
164 | | (BOOLEAN(APIENTRY*)(void*, ULONG))GetProcAddress(hLib, "SystemFunction036"); |
165 | | if (!Random::RtlGenRandom) |
166 | | { |
167 | | WarningLog(<< "Not linked with ADVAPI32.DLL, using srand(..) and rand() for random numbers"); |
168 | | } |
169 | | } |
170 | | #endif // RESIP_RANDOM_WIN32_RTL |
171 | | |
172 | | mIsInitialized = true; |
173 | | } |
174 | | } |
175 | | #endif // not dead code |
176 | | |
177 | | #else // WIN32 |
178 | | // ?dcm? -- OpenSSL will transparently initialize PRNG if /dev/urandom is |
179 | | // present. In any case, will move into OpenSSLInit |
180 | 19.9k | if ( !Random::mIsInitialized) |
181 | 1 | { |
182 | 1 | Lock lock(mMutex); |
183 | 1 | if (!Random::mIsInitialized) |
184 | 1 | { |
185 | 1 | mIsInitialized = true; |
186 | 1 | Timer::setupTimeOffsets(); |
187 | | |
188 | 1 | unsigned seed = getSimpleSeed(); |
189 | | |
190 | 1 | int fd = open("/dev/urandom", O_RDONLY); |
191 | | // !ah! blocks on embedded devices -- not enough entropy. |
192 | 1 | if ( fd != -1 ) |
193 | 1 | { |
194 | | // Mix kernel entropy into the PRNG seed before seeding so that |
195 | | // random()/rand() are not seeded solely from time+pid, which an |
196 | | // attacker can approximate (CWE-338). XOR preserves the per-thread |
197 | | // and per-process distinction that getSimpleSeed() provides. |
198 | 1 | unsigned urandomSeed = 0; |
199 | 1 | int s = read( fd,&urandomSeed,sizeof(urandomSeed) ); //!ah! blocks if /dev/random on embedded sys |
200 | | |
201 | 1 | if ( s == sizeof(urandomSeed) ) |
202 | 1 | { |
203 | 1 | seed ^= urandomSeed; |
204 | 1 | } |
205 | 0 | else |
206 | 0 | { |
207 | 0 | ErrLog( << "System is short of randomness" ); // !ah! never prints |
208 | 0 | } |
209 | 1 | } |
210 | 0 | else |
211 | 0 | { |
212 | 0 | ErrLog( << "Could not open /dev/urandom" ); |
213 | 0 | } |
214 | | |
215 | | #if defined(RESIP_RANDOM_THREAD_LOCAL) |
216 | | sThreadSeedBase = seed; |
217 | | ThreadIf::tlsKeyCreate(sRandomStateKey, ::free); |
218 | | #elif defined(RESIP_RANDOM_THREAD_MUTEX) |
219 | | struct random_data *buf; |
220 | | size_t sz = sizeof(*buf)+RANDOM_STATE_SIZE; |
221 | | buf = (struct random_data*) ::malloc(sz); |
222 | | memset( buf, 0, sz); // .kw. strange segfaults without this |
223 | | initstate_r(seed, ((char*)buf)+sizeof(*buf), RANDOM_STATE_SIZE, buf); |
224 | | sRandomState = buf; |
225 | | #else |
226 | 1 | srandom(seed); |
227 | 1 | #endif |
228 | | |
229 | | #if defined(USE_SSL) |
230 | | if (fd == -1 ) |
231 | | { |
232 | | // really bad sign - /dev/random does not exist so need to intialize |
233 | | // OpenSSL some other way |
234 | | |
235 | | // !cj! need to fix assert(0); |
236 | | } |
237 | | else |
238 | | { |
239 | | char buf[1024/8]; // size is number byes used for OpenSSL init |
240 | | |
241 | | int s = read( fd,&buf,sizeof(buf) ); |
242 | | |
243 | | if ( s != sizeof(buf) ) |
244 | | { |
245 | | ErrLog( << "System is short of randomness" ); |
246 | | } |
247 | | |
248 | | RAND_add(buf,sizeof(buf),double(s*8)); |
249 | | } |
250 | | #endif // SSL |
251 | 1 | if (fd != -1 ) |
252 | 1 | { |
253 | 1 | ::close(fd); |
254 | 1 | } |
255 | 1 | } |
256 | 1 | } |
257 | 19.9k | #endif // not WIN32 |
258 | 19.9k | } |
259 | | |
260 | | int |
261 | | Random::getRandom() |
262 | 13.2k | { |
263 | 13.2k | initialize(); |
264 | | |
265 | | #ifdef WIN32 |
266 | | |
267 | | int ret = 0; |
268 | | |
269 | | #ifdef RESIP_RANDOM_WIN32_RTL |
270 | | // see comment in initialize() |
271 | | if (Random::RtlGenRandom) |
272 | | { |
273 | | unsigned long buff[1]; |
274 | | ULONG ulCbBuff = sizeof(buff); |
275 | | if (Random::RtlGenRandom(buff,ulCbBuff)) |
276 | | { |
277 | | // .kw. all other impls here return positive number, so do the same... |
278 | | ret = buff[0] & (~(1<<31)); |
279 | | return ret; |
280 | | } |
281 | | } |
282 | | // fallback to using rand() if this is a Windows version previous to XP |
283 | | #endif // RESIP_RANDOM_WIN32_RTL |
284 | | { |
285 | | // rand() returns [0,RAND_MAX], which on Windows is 15 bits and positive |
286 | | // code below gets 30bits of randomness; with bit31 and bit15 |
287 | | // always zero; result is always positive |
288 | | resip_assert( RAND_MAX == 0x7fff ); |
289 | | // WATCHOUT: on Linux, rand() returns 31bits, and assert above will fail |
290 | | int r1 = rand(); |
291 | | int r2 = rand(); |
292 | | ret = (r1<<16) + r2; |
293 | | } |
294 | | |
295 | | return ret; |
296 | | #else // WIN32 |
297 | | |
298 | | #if defined(RESIP_RANDOM_THREAD_LOCAL) |
299 | | struct random_data *buf = (struct random_data*) ThreadIf::tlsGetValue(sRandomStateKey); |
300 | | if ( buf==NULL ) { |
301 | | size_t sz = sizeof(*buf)+RANDOM_STATE_SIZE; |
302 | | buf = (struct random_data*) ::malloc(sz); |
303 | | memset( buf, 0, sz); // .kw. strange segfaults without this |
304 | | // Mix in the /dev/urandom-derived base captured at initialize() time so |
305 | | // per-thread seeds are not derived solely from time+pid (CWE-338). |
306 | | unsigned seed = getSimpleSeed() ^ sThreadSeedBase; |
307 | | initstate_r(seed, ((char*)buf)+sizeof(*buf), RANDOM_STATE_SIZE, buf); |
308 | | ThreadIf::tlsSetValue(sRandomStateKey, buf); |
309 | | } |
310 | | int32_t ret; |
311 | | random_r(buf, &ret); |
312 | | return ret; |
313 | | #elif defined(RESIP_RANDOM_THREAD_MUTEX) |
314 | | int32_t ret; |
315 | | { |
316 | | Lock statelock(mMutex); |
317 | | random_r(sRandomState, &ret); |
318 | | } |
319 | | return ret; |
320 | | #else |
321 | | // random returns [0,RAN_MAX]. On Linux, this is 31 bits and positive. |
322 | | // On some platforms it might be on 15 bits, and will need to do something. |
323 | | // assert( RAND_MAX == ((1<<31)-1) ); // ?slg? commented out assert since, RAND_MAX is not used in random(), it applies to rand() only |
324 | 13.2k | return random(); |
325 | 13.2k | #endif // THREAD_LOCAL |
326 | 13.2k | #endif // WIN32 |
327 | 13.2k | } |
328 | | |
329 | | int |
330 | | Random::getCryptoRandom() |
331 | 0 | { |
332 | 0 | initialize(); |
333 | |
|
334 | | #if USE_OPENSSL |
335 | | int ret; |
336 | | int e = RAND_bytes( (unsigned char*)&ret , sizeof(ret) ); |
337 | | if ( e < 0 ) |
338 | | { |
339 | | // error of some type - likely not enough rendomness to dod this |
340 | | long err = ERR_get_error(); |
341 | | |
342 | | char buf[1024]; |
343 | | ERR_error_string_n(err,buf,sizeof(buf)); |
344 | | |
345 | | ErrLog( << buf ); |
346 | | resip_assert(0); |
347 | | } |
348 | | return ret; |
349 | | #else |
350 | 0 | return getRandom(); |
351 | 0 | #endif |
352 | 0 | } |
353 | | |
354 | | Data |
355 | | Random::getRandom(unsigned int len) |
356 | 6.64k | { |
357 | 6.64k | initialize(); |
358 | 6.64k | resip_assert(len < Random::maxLength+1); |
359 | | |
360 | 6.64k | union |
361 | 6.64k | { |
362 | 6.64k | char cbuf[Random::maxLength+1]; |
363 | 6.64k | unsigned int ibuf[(Random::maxLength+1)/sizeof(int)]; |
364 | 6.64k | }; |
365 | | |
366 | 19.9k | for (unsigned int count=0; count<(len+sizeof(int)-1)/sizeof(int); ++count) |
367 | 13.2k | { |
368 | 13.2k | ibuf[count] = Random::getRandom(); |
369 | 13.2k | } |
370 | 6.64k | return Data(cbuf, len); |
371 | 6.64k | } |
372 | | |
373 | | Data |
374 | | Random::getCryptoRandom(unsigned int len) |
375 | 0 | { |
376 | 0 | unsigned char* buf = new unsigned char[len]; |
377 | 0 | getCryptoRandom(buf, len); // USE_SSL check is in here |
378 | 0 | return Data(Data::Take, (char*)buf, len); |
379 | 0 | } |
380 | | |
381 | | Data |
382 | | Random::getRandomHex(unsigned int numBytes) |
383 | 6.64k | { |
384 | 6.64k | return Random::getRandom(numBytes).hex(); |
385 | 6.64k | } |
386 | | |
387 | | Data |
388 | | Random::getRandomBase64(unsigned int numBytes) |
389 | 0 | { |
390 | 0 | return Random::getRandom(numBytes).base64encode(); |
391 | 0 | } |
392 | | |
393 | | Data |
394 | | Random::getCryptoRandomHex(unsigned int numBytes) |
395 | 0 | { |
396 | 0 | return Random::getCryptoRandom(numBytes).hex(); |
397 | 0 | } |
398 | | |
399 | | Data |
400 | | Random::getCryptoRandomBase64(unsigned int numBytes) |
401 | 0 | { |
402 | 0 | return Random::getCryptoRandom(numBytes).base64encode(); |
403 | 0 | } |
404 | | |
405 | | /* |
406 | | [From RFC 4122] |
407 | | |
408 | | The version 4 UUID is meant for generating UUIDs from truly-random or |
409 | | pseudo-random numbers. |
410 | | |
411 | | The algorithm is as follows: |
412 | | |
413 | | o Set the two most significant bits (bits 6 and 7) of the |
414 | | clock_seq_hi_and_reserved to zero and one, respectively. |
415 | | |
416 | | o Set the four most significant bits (bits 12 through 15) of the |
417 | | time_hi_and_version field to the 4-bit version number from |
418 | | Section 4.1.3. (0 1 0 0) |
419 | | |
420 | | o Set all the other bits to randomly (or pseudo-randomly) chosen |
421 | | values. |
422 | | |
423 | | UUID = time-low "-" time-mid "-" |
424 | | time-high-and-version "-" |
425 | | clock-seq-and-reserved |
426 | | clock-seq-low "-" node |
427 | | time-low = 4hexOctet |
428 | | time-mid = 2hexOctet |
429 | | time-high-and-version = 2hexOctet |
430 | | clock-seq-and-reserved = hexOctet |
431 | | clock-seq-low = hexOctet |
432 | | node = 6hexOctet |
433 | | hexOctet = hexDigit hexDigit |
434 | | */ |
435 | | Data |
436 | | Random::getVersion4UuidUrn() |
437 | 0 | { |
438 | 0 | Data urn ("urn:uuid:"); |
439 | 0 | urn += getCryptoRandomHex(4); // time-low |
440 | 0 | urn += "-"; |
441 | 0 | urn += getCryptoRandomHex(2); // time-mid |
442 | 0 | urn += "-"; |
443 | |
|
444 | 0 | Data time_hi_and_version = Random::getCryptoRandom(2); |
445 | 0 | time_hi_and_version[0] &= 0x0f; |
446 | 0 | time_hi_and_version[0] |= 0x40; |
447 | 0 | urn += time_hi_and_version.hex(); |
448 | |
|
449 | 0 | urn += "-"; |
450 | |
|
451 | 0 | Data clock_seq_hi_and_reserved = Random::getCryptoRandom(1); |
452 | 0 | clock_seq_hi_and_reserved[0] &= 0x3f; |
453 | 0 | clock_seq_hi_and_reserved[0] |= 0x40; |
454 | 0 | urn += clock_seq_hi_and_reserved.hex(); |
455 | |
|
456 | 0 | urn += getCryptoRandomHex(1); // clock-seq-low |
457 | 0 | urn += "-"; |
458 | 0 | urn += getCryptoRandomHex(6); // node |
459 | 0 | return urn; |
460 | 0 | } |
461 | | |
462 | | void |
463 | | Random::getCryptoRandom(unsigned char* buf, unsigned int numBytes) |
464 | 0 | { |
465 | 0 | resip_assert(numBytes < Random::maxLength+1); |
466 | |
|
467 | | #if USE_OPENSSL |
468 | | initialize(); |
469 | | int e = RAND_bytes( (unsigned char*)buf , numBytes ); |
470 | | if ( e < 0 ) |
471 | | { |
472 | | // error of some type - likely not enough rendomness to dod this |
473 | | long err = ERR_get_error(); |
474 | | |
475 | | char buf[1024]; |
476 | | ERR_error_string_n(err,buf,sizeof(buf)); |
477 | | |
478 | | ErrLog( << buf ); |
479 | | resip_assert(0); |
480 | | } |
481 | | #else |
482 | | // !bwc! Should optimize this. |
483 | 0 | Data temp=Random::getRandom(numBytes); |
484 | 0 | memcpy(buf, temp.data(), numBytes); |
485 | 0 | #endif |
486 | 0 | } |
487 | | |
488 | | #ifdef WIN32 |
489 | | Random::Initializer::Initializer() : mThreadStorage(::TlsAlloc()) |
490 | | { |
491 | | resip_assert(mThreadStorage != TLS_OUT_OF_INDEXES); |
492 | | } |
493 | | |
494 | | Random::Initializer::~Initializer() |
495 | | { |
496 | | ::TlsFree(mThreadStorage); |
497 | | } |
498 | | |
499 | | void |
500 | | Random::Initializer::setInitialized() |
501 | | { |
502 | | ::TlsSetValue(mThreadStorage, (LPVOID) TRUE); |
503 | | } |
504 | | |
505 | | bool |
506 | | Random::Initializer::isInitialized() |
507 | | { |
508 | | #pragma warning ( disable:4311) |
509 | | #pragma warning ( disable:4302) |
510 | | // Note: if value is not set yet then 0 (false) is returned |
511 | | return (BOOL) ::TlsGetValue(mThreadStorage) == TRUE; |
512 | | } |
513 | | #endif |
514 | | |
515 | | |
516 | | /* ==================================================================== |
517 | | * The Vovida Software License, Version 1.0 |
518 | | * |
519 | | * Copyright (c) 2005. All rights reserved. |
520 | | * |
521 | | * Redistribution and use in source and binary forms, with or without |
522 | | * modification, are permitted provided that the following conditions |
523 | | * are met: |
524 | | * |
525 | | * 1. Redistributions of source code must retain the above copyright |
526 | | * notice, this list of conditions and the following disclaimer. |
527 | | * |
528 | | * 2. Redistributions in binary form must reproduce the above copyright |
529 | | * notice, this list of conditions and the following disclaimer in |
530 | | * the documentation and/or other materials provided with the |
531 | | * distribution. |
532 | | * |
533 | | * 3. The names "VOCAL", "Vovida Open Communication Application Library", |
534 | | * and "Vovida Open Communication Application Library (VOCAL)" must |
535 | | * not be used to endorse or promote products derived from this |
536 | | * software without prior written permission. For written |
537 | | * permission, please contact vocal@vovida.org. |
538 | | * |
539 | | * 4. Products derived from this software may not be called "VOCAL", nor |
540 | | * may "VOCAL" appear in their name, without prior written |
541 | | * permission of Vovida Networks, Inc. |
542 | | * |
543 | | * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED |
544 | | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
545 | | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND |
546 | | * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA |
547 | | * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES |
548 | | * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, |
549 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
550 | | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
551 | | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
552 | | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
553 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
554 | | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
555 | | * DAMAGE. |
556 | | * |
557 | | * ==================================================================== |
558 | | * |
559 | | * This software consists of voluntary contributions made by Vovida |
560 | | * Networks, Inc. and many individuals on behalf of Vovida Networks, |
561 | | * Inc. For more information on Vovida Networks, Inc., please see |
562 | | * <http://www.vovida.org/>. |
563 | | * |
564 | | * vi: set shiftwidth=3 expandtab: |
565 | | */ |