/[winpt]/trunk/Gnupg/rndw32.c
ViewVC logotype

Annotation of /trunk/Gnupg/rndw32.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2 - (hide annotations)
Mon Jan 31 11:02:21 2005 UTC (20 years, 1 month ago) by twoaday
File MIME type: text/plain
File size: 22297 byte(s)
WinPT initial checkin.


1 twoaday 2 /* rndw32.c - W32 entropy gatherer
2     * Copyright (C) 1999, 2000 Free Software Foundation, Inc.
3     * Copyright Peter Gutmann, Matt Thomlinson and Blake Coverett 1996-1999
4     *
5     * This file is part of Libgcrypt.
6     *
7     * Libgcrypt is free software; you can redistribute it and/or modify
8     * it under the terms of the GNU General Public License as published by
9     * the Free Software Foundation; either version 2 of the License, or
10     * (at your option) any later version.
11     *
12     * Libgcrypt is distributed in the hope that it will be useful,
13     * but WITHOUT ANY WARRANTY; without even the implied warranty of
14     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15     * GNU General Public License for more details.
16     *
17     * You should have received a copy of the GNU General Public License
18     * along with this program; if not, write to the Free Software
19     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20     *
21     *************************************************************************
22     * The code here is based on code from Cryptlib 3.0 beta by Peter Gutmann.
23     * Source file misc/rndwin32.c "Win32 Randomness-Gathering Code" with this
24     * copyright notice:
25     *
26     * This module is part of the cryptlib continuously seeded pseudorandom
27     * number generator. For usage conditions, see lib_rand.c
28     *
29     * [Here is the notice from lib_rand.c, which is now called dev_sys.c]
30     *
31     * This module and the misc/rnd*.c modules represent the cryptlib
32     * continuously seeded pseudorandom number generator (CSPRNG) as described in
33     * my 1998 Usenix Security Symposium paper "The generation of random numbers
34     * for cryptographic purposes".
35     *
36     * The CSPRNG code is copyright Peter Gutmann (and various others) 1996,
37     * 1997, 1998, 1999, all rights reserved. Redistribution of the CSPRNG
38     * modules and use in source and binary forms, with or without modification,
39     * are permitted provided that the following conditions are met:
40     *
41     * 1. Redistributions of source code must retain the above copyright notice
42     * and this permission notice in its entirety.
43     *
44     * 2. Redistributions in binary form must reproduce the copyright notice in
45     * the documentation and/or other materials provided with the distribution.
46     *
47     * 3. A copy of any bugfixes or enhancements made must be provided to the
48     * author, <[email protected]> to allow them to be added to the
49     * baseline version of the code.
50     *
51     * ALTERNATIVELY, the code may be distributed under the terms of the GNU
52     * General Public License, version 2 or any later version published by the
53     * Free Software Foundation, in which case the provisions of the GNU GPL are
54     * required INSTEAD OF the above restrictions.
55     *
56     * Although not required under the terms of the GPL, it would still be nice if
57     * you could make any changes available to the author to allow a consistent
58     * code base to be maintained
59     *************************************************************************
60     * Heavily modified by Timo Schulz to fit into WinPT, 2001.
61     */
62    
63     #include <stdio.h>
64     #include <stdlib.h>
65     #include <assert.h>
66     #include <errno.h>
67     #include <string.h>
68    
69     #include <windows.h>
70     #include <tlhelp32.h>
71     #include <winioctl.h>
72    
73    
74     /* Type definitions for function pointers to call Toolhelp32 functions
75     * used with the windows95 gatherer */
76     typedef BOOL (WINAPI * MODULEWALK) (HANDLE hSnapshot, MODULEENTRY32 *lpme);
77     typedef BOOL (WINAPI * THREADWALK) (HANDLE hSnapshot, THREADENTRY32 *lpte);
78     typedef BOOL (WINAPI * PROCESSWALK) (HANDLE hSnapshot, PROCESSENTRY32 *lppe);
79     typedef BOOL (WINAPI * HEAPLISTWALK) (HANDLE hSnapshot, HEAPLIST32 *lphl);
80     typedef BOOL (WINAPI * HEAPFIRST) (HEAPENTRY32 *lphe, DWORD th32ProcessID,
81     DWORD th32HeapID);
82     typedef BOOL (WINAPI * HEAPNEXT) (HEAPENTRY32 *lphe);
83     typedef HANDLE (WINAPI * CREATESNAPSHOT) (DWORD dwFlags, DWORD th32ProcessID);
84    
85     /* Type definitions for function pointers to call NetAPI32 functions */
86     typedef DWORD (WINAPI * NETSTATISTICSGET) (LPWSTR szServer, LPWSTR szService,
87     DWORD dwLevel, DWORD dwOptions,
88     LPBYTE * lpBuffer);
89     typedef DWORD (WINAPI * NETAPIBUFFERSIZE) (LPVOID lpBuffer, LPDWORD cbBuffer);
90     typedef DWORD (WINAPI * NETAPIBUFFERFREE) (LPVOID lpBuffer);
91    
92    
93     /* When we query the performance counters, we allocate an initial buffer and
94     * then reallocate it as required until RegQueryValueEx() stops returning
95     * ERROR_MORE_DATA. The following values define the initial buffer size and
96     * step size by which the buffer is increased
97     */
98     #define PERFORMANCE_BUFFER_SIZE 65536 /* Start at 64K */
99     #define PERFORMANCE_BUFFER_STEP 16384 /* Step by 16K */
100    
101    
102     static void
103     random_w32_err( const char *format, ... )
104     {
105     char log[8192];
106     va_list arg_ptr;
107    
108     va_start( arg_ptr, format );
109     _vsnprintf( log, sizeof log-1, format, arg_ptr );
110     MessageBox( NULL, log, "Crypto RNG", MB_ICONERROR|MB_OK );
111     va_end( arg_ptr );
112     } /* log_box */
113    
114    
115     static void
116     slow_gatherer_windows95( void (*add)(const void*, size_t, int), int requester )
117     {
118     static CREATESNAPSHOT pCreateToolhelp32Snapshot = NULL;
119     static MODULEWALK pModule32First = NULL;
120     static MODULEWALK pModule32Next = NULL;
121     static PROCESSWALK pProcess32First = NULL;
122     static PROCESSWALK pProcess32Next = NULL;
123     static THREADWALK pThread32First = NULL;
124     static THREADWALK pThread32Next = NULL;
125     static HEAPLISTWALK pHeap32ListFirst = NULL;
126     static HEAPLISTWALK pHeap32ListNext = NULL;
127     static HEAPFIRST pHeap32First = NULL;
128     static HEAPNEXT pHeap32Next = NULL;
129     HANDLE hSnapshot;
130    
131    
132     /* initialize the Toolhelp32 function pointers */
133     if ( !pCreateToolhelp32Snapshot ) {
134     HANDLE hKernel;
135    
136     /* Obtain the module handle of the kernel to retrieve the addresses
137     * of the Toolhelp32 functions */
138     if ( ( !(hKernel = GetModuleHandle ("KERNEL32.DLL"))) ) {
139     random_w32_err( "rndw32: can't get module handle" );
140     return;
141     }
142    
143     /* Now get pointers to the functions */
144     pCreateToolhelp32Snapshot = (CREATESNAPSHOT) GetProcAddress (hKernel,
145     "CreateToolhelp32Snapshot");
146     pModule32First = (MODULEWALK) GetProcAddress (hKernel, "Module32First");
147     pModule32Next = (MODULEWALK) GetProcAddress (hKernel, "Module32Next");
148     pProcess32First = (PROCESSWALK) GetProcAddress (hKernel,
149     "Process32First");
150     pProcess32Next = (PROCESSWALK) GetProcAddress (hKernel,
151     "Process32Next");
152     pThread32First = (THREADWALK) GetProcAddress (hKernel, "Thread32First");
153     pThread32Next = (THREADWALK) GetProcAddress (hKernel, "Thread32Next");
154     pHeap32ListFirst = (HEAPLISTWALK) GetProcAddress (hKernel,
155     "Heap32ListFirst");
156     pHeap32ListNext = (HEAPLISTWALK) GetProcAddress (hKernel,
157     "Heap32ListNext");
158     pHeap32First = (HEAPFIRST) GetProcAddress (hKernel, "Heap32First");
159     pHeap32Next = (HEAPNEXT) GetProcAddress (hKernel, "Heap32Next");
160    
161     if ( !pCreateToolhelp32Snapshot
162     || !pModule32First || !pModule32Next
163     || !pProcess32First || !pProcess32Next
164     || !pThread32First || !pThread32Next
165     || !pHeap32ListFirst || !pHeap32ListNext
166     || !pHeap32First || !pHeap32Next ) {
167     random_w32_err( "rndw32: failed to get a toolhep function" );
168     return;
169     }
170     }
171    
172     /* Take a snapshot of everything we can get to which is currently
173     * in the system */
174     if ( !(hSnapshot = pCreateToolhelp32Snapshot (TH32CS_SNAPALL, 0)) ) {
175     random_w32_err( "rndw32: failed to take a toolhelp snapshot" );
176     return;
177     }
178    
179     /* Walk through the local heap */
180     { HEAPLIST32 hl32;
181     hl32.dwSize = sizeof (HEAPLIST32);
182     if (pHeap32ListFirst (hSnapshot, &hl32)) {
183     do {
184     HEAPENTRY32 he32;
185    
186     /* First add the information from the basic Heaplist32 struct */
187     (*add) ( &hl32, sizeof (hl32), requester );
188    
189     /* Now walk through the heap blocks getting information
190     * on each of them */
191     he32.dwSize = sizeof (HEAPENTRY32);
192     if (pHeap32First (&he32, hl32.th32ProcessID, hl32.th32HeapID)){
193     do {
194     (*add) ( &he32, sizeof (he32), requester );
195     } while (pHeap32Next (&he32));
196     }
197     } while (pHeap32ListNext (hSnapshot, &hl32));
198     }
199     }
200    
201    
202     /* Walk through all processes */
203     { PROCESSENTRY32 pe32;
204     pe32.dwSize = sizeof (PROCESSENTRY32);
205     if (pProcess32First (hSnapshot, &pe32)) {
206     do {
207     (*add) ( &pe32, sizeof (pe32), requester );
208     } while (pProcess32Next (hSnapshot, &pe32));
209     }
210     }
211    
212     /* Walk through all threads */
213     { THREADENTRY32 te32;
214     te32.dwSize = sizeof (THREADENTRY32);
215     if (pThread32First (hSnapshot, &te32)) {
216     do {
217     (*add) ( &te32, sizeof (te32), requester );
218     } while (pThread32Next (hSnapshot, &te32));
219     }
220     }
221    
222     /* Walk through all modules associated with the process */
223     { MODULEENTRY32 me32;
224     me32.dwSize = sizeof (MODULEENTRY32);
225     if (pModule32First (hSnapshot, &me32)) {
226     do {
227     (*add) ( &me32, sizeof (me32), requester );
228     } while (pModule32Next (hSnapshot, &me32));
229     }
230     }
231    
232     CloseHandle (hSnapshot);
233     }
234    
235    
236    
237    
238    
239     static void
240     slow_gatherer_windowsNT( void (*add)(const void*, size_t, int), int requester )
241     {
242     static int is_initialized = 0;
243     static NETSTATISTICSGET pNetStatisticsGet = NULL;
244     static NETAPIBUFFERSIZE pNetApiBufferSize = NULL;
245     static NETAPIBUFFERFREE pNetApiBufferFree = NULL;
246     static int is_workstation = 1;
247     static int diskperf_warning = 0;
248    
249     static int cbPerfData = PERFORMANCE_BUFFER_SIZE;
250     PERF_DATA_BLOCK *pPerfData;
251     HANDLE hDevice, hNetAPI32 = NULL;
252     DWORD dwSize, status;
253     int nDrive;
254    
255     if ( !is_initialized ) {
256     HKEY hKey;
257    
258     /* Find out whether this is an NT server or workstation if necessary */
259     if (RegOpenKeyEx (HKEY_LOCAL_MACHINE,
260     "SYSTEM\\CurrentControlSet\\Control\\ProductOptions",
261     0, KEY_READ, &hKey) == ERROR_SUCCESS) {
262     BYTE szValue[32];
263     dwSize = sizeof (szValue);
264    
265     status = RegQueryValueEx (hKey, "ProductType", 0, NULL,
266     szValue, &dwSize);
267     if (status == ERROR_SUCCESS && stricmp (szValue, "WinNT")) {
268     /* Note: There are (at least) three cases for ProductType:
269     * WinNT = NT Workstation, ServerNT = NT Server, LanmanNT =
270     * NT Server acting as a Domain Controller */
271     is_workstation = 0;
272     }
273     RegCloseKey (hKey);
274     }
275    
276     /* Initialize the NetAPI32 function pointers if necessary */
277     if ( (hNetAPI32 = LoadLibrary ("NETAPI32.DLL")) ) {
278     pNetStatisticsGet = (NETSTATISTICSGET) GetProcAddress (hNetAPI32,
279     "NetStatisticsGet");
280     pNetApiBufferSize = (NETAPIBUFFERSIZE) GetProcAddress (hNetAPI32,
281     "NetApiBufferSize");
282     pNetApiBufferFree = (NETAPIBUFFERFREE) GetProcAddress (hNetAPI32,
283     "NetApiBufferFree");
284    
285     if ( !pNetStatisticsGet
286     || !pNetApiBufferSize || !pNetApiBufferFree ) {
287     FreeLibrary (hNetAPI32);
288     hNetAPI32 = NULL;
289     }
290     }
291    
292     is_initialized = 1;
293     }
294    
295     /* Get network statistics. Note: Both NT Workstation and NT Server by
296     * default will be running both the workstation and server services. The
297     * heuristic below is probably useful though on the assumption that the
298     * majority of the network traffic will be via the appropriate service.
299     * In any case the network statistics return almost no randomness */
300     { LPBYTE lpBuffer;
301     if (hNetAPI32 && !pNetStatisticsGet (NULL,
302     is_workstation ? L"LanmanWorkstation" :
303     L"LanmanServer", 0, 0, &lpBuffer) ) {
304     pNetApiBufferSize (lpBuffer, &dwSize);
305     (*add) ( lpBuffer, dwSize,requester );
306     pNetApiBufferFree (lpBuffer);
307     }
308     }
309    
310     /* Get disk I/O statistics for all the hard drives */
311     for (nDrive = 0;; nDrive++) {
312     DISK_PERFORMANCE diskPerformance;
313     char szDevice[50];
314    
315     /* Check whether we can access this device */
316     sprintf (szDevice, "\\\\.\\PhysicalDrive%d", nDrive);
317     hDevice = CreateFile (szDevice, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
318     NULL, OPEN_EXISTING, 0, NULL);
319     if (hDevice == INVALID_HANDLE_VALUE)
320     break;
321    
322     /* Note: This only works if you have turned on the disk performance
323     * counters with 'diskperf -y'. These counters are off by default */
324     if (DeviceIoControl (hDevice, IOCTL_DISK_PERFORMANCE, NULL, 0,
325     &diskPerformance, sizeof (DISK_PERFORMANCE),
326     &dwSize, NULL))
327     {
328     (*add) ( &diskPerformance, dwSize, requester );
329     }
330     CloseHandle (hDevice);
331     }
332    
333     /* Get information from the system performance counters. This can take
334     * a few seconds to do. In some environments the call to
335     * RegQueryValueEx() can produce an access violation at some random time
336     * in the future, adding a short delay after the following code block
337     * makes the problem go away. This problem is extremely difficult to
338     * reproduce, I haven't been able to get it to occur despite running it
339     * on a number of machines. The best explanation for the problem is that
340     * on the machine where it did occur, it was caused by an external driver
341     * or other program which adds its own values under the
342     * HKEY_PERFORMANCE_DATA key. The NT kernel calls the required external
343     * modules to map in the data, if there's a synchronisation problem the
344     * external module would write its data at an inappropriate moment,
345     * causing the access violation. A low-level memory checker indicated
346     * that ExpandEnvironmentStrings() in KERNEL32.DLL, called an
347     * interminable number of calls down inside RegQueryValueEx(), was
348     * overwriting memory (it wrote twice the allocated size of a buffer to a
349     * buffer allocated by the NT kernel). This may be what's causing the
350     * problem, but since it's in the kernel there isn't much which can be
351     * done.
352     *
353     * In addition to these problems the code in RegQueryValueEx() which
354     * estimates the amount of memory required to return the performance
355     * counter information isn't very accurate, since it always returns a
356     * worst-case estimate which is usually nowhere near the actual amount
357     * required. For example it may report that 128K of memory is required,
358     * but only return 64K of data */
359     { pPerfData = malloc (cbPerfData);
360     for (;;) {
361     dwSize = cbPerfData;
362     status = RegQueryValueEx (HKEY_PERFORMANCE_DATA, "Global", NULL,
363     NULL, (LPBYTE) pPerfData, &dwSize);
364     if (status == ERROR_SUCCESS) {
365     if (!memcmp (pPerfData->Signature, L"PERF", 8)) {
366     (*add) ( pPerfData, dwSize, requester );
367     }
368     break;
369     }
370     else if (status == ERROR_MORE_DATA) {
371     cbPerfData += PERFORMANCE_BUFFER_STEP;
372     pPerfData = realloc (pPerfData, cbPerfData);
373     }
374     else {
375     random_w32_err ( "rndw32: get performance data problem");
376     break;
377     }
378     }
379     free (pPerfData);
380     }
381     /* Although this isn't documented in the Win32 API docs, it's necessary
382     to explicitly close the HKEY_PERFORMANCE_DATA key after use (it's
383     implicitly opened on the first call to RegQueryValueEx()). If this
384     isn't done then any system components which provide performance data
385     can't be removed or changed while the handle remains active */
386     RegCloseKey (HKEY_PERFORMANCE_DATA);
387     }
388    
389    
390     int
391     gather_random( void (*add)(const void*, size_t, int), int requester,
392     size_t length, int level )
393     {
394     static int is_initialized;
395     static int is_windows95;
396    
397    
398     if( !level )
399     return 0;
400     /* We don't differentiate between level 1 and 2 here because
401     * there is no nternal entropy pool as a scary resource. It may
402     * all work slower, but because our entropy source will never
403     * block but deliver some not easy to measure entropy, we assume level 2
404     */
405    
406    
407     if ( !is_initialized ) {
408     OSVERSIONINFO osvi = { sizeof( osvi ) };
409     DWORD platform;
410    
411     GetVersionEx( &osvi );
412     platform = osvi.dwPlatformId;
413     is_windows95 = platform == VER_PLATFORM_WIN32_WINDOWS;
414    
415     if ( platform == VER_PLATFORM_WIN32s ) {
416     random_w32_err("can't run on a W32s platform" );
417     return 0;
418     }
419     is_initialized = 1;
420     }
421    
422     if (is_windows95 ) {
423     slow_gatherer_windows95( add, requester );
424     }
425     else {
426     slow_gatherer_windowsNT( add, requester );
427     }
428    
429     return 0;
430     }
431    
432    
433    
434     int
435     gather_random_fast( void (*add)(const void*, size_t, int), int requester )
436     {
437     static int addedFixedItems = 0;
438    
439     /* Get various basic pieces of system information: Handle of active
440     * window, handle of window with mouse capture, handle of clipboard owner
441     * handle of start of clpboard viewer list, pseudohandle of current
442     * process, current process ID, pseudohandle of current thread, current
443     * thread ID, handle of desktop window, handle of window with keyboard
444     * focus, whether system queue has any events, cursor position for last
445     * message, 1 ms time for last message, handle of window with clipboard
446     * open, handle of process heap, handle of procs window station, types of
447     * events in input queue, and milliseconds since Windows was started */
448     { BYTE buffer[20*sizeof(DWORD)], *bufptr;
449     bufptr = buffer;
450     #define ADD(f) do { DWORD along = (DWORD)(f); \
451     memcpy (bufptr, &along, sizeof (along) ); \
452     bufptr += sizeof (along); } while (0)
453     ADD ( GetActiveWindow ());
454     ADD ( GetCapture ());
455     ADD ( GetClipboardOwner ());
456     ADD ( GetClipboardViewer ());
457     ADD ( GetCurrentProcess ());
458     ADD ( GetCurrentProcessId ());
459     ADD ( GetCurrentThread ());
460     ADD ( GetCurrentThreadId ());
461     ADD ( GetDesktopWindow ());
462     ADD ( GetFocus ());
463     ADD ( GetInputState ());
464     ADD ( GetMessagePos ());
465     ADD ( GetMessageTime ());
466     ADD ( GetOpenClipboardWindow ());
467     ADD ( GetProcessHeap ());
468     ADD ( GetProcessWindowStation ());
469     ADD ( GetQueueStatus (QS_ALLEVENTS));
470     ADD ( GetTickCount ());
471    
472     assert ( bufptr-buffer < sizeof (buffer) );
473     (*add) ( buffer, bufptr-buffer, requester );
474     #undef ADD
475     }
476    
477     /* Get multiword system information: Current caret position, current
478     * mouse cursor position */
479     { POINT point;
480     GetCaretPos (&point);
481     (*add) ( &point, sizeof (point), requester );
482     GetCursorPos (&point);
483     (*add) ( &point, sizeof (point), requester );
484     }
485    
486     /* Get percent of memory in use, bytes of physical memory, bytes of free
487     * physical memory, bytes in paging file, free bytes in paging file, user
488     * bytes of address space, and free user bytes */
489     { MEMORYSTATUS memoryStatus;
490     memoryStatus.dwLength = sizeof (MEMORYSTATUS);
491     GlobalMemoryStatus (&memoryStatus);
492     (*add) ( &memoryStatus, sizeof (memoryStatus), requester );
493     }
494    
495     /* Get thread and process creation time, exit time, time in kernel mode,
496     and time in user mode in 100ns intervals */
497     { HANDLE handle;
498     FILETIME creationTime, exitTime, kernelTime, userTime;
499     DWORD minimumWorkingSetSize, maximumWorkingSetSize;
500    
501     handle = GetCurrentThread ();
502     GetThreadTimes (handle, &creationTime, &exitTime,
503     &kernelTime, &userTime);
504     (*add) ( &creationTime, sizeof (creationTime), requester );
505     (*add) ( &exitTime, sizeof (exitTime), requester );
506     (*add) ( &kernelTime, sizeof (kernelTime), requester );
507     (*add) ( &userTime, sizeof (userTime), requester );
508    
509     handle = GetCurrentProcess ();
510     GetProcessTimes (handle, &creationTime, &exitTime,
511     &kernelTime, &userTime);
512     (*add) ( &creationTime, sizeof (creationTime), requester );
513     (*add) ( &exitTime, sizeof (exitTime), requester );
514     (*add) ( &kernelTime, sizeof (kernelTime), requester );
515     (*add) ( &userTime, sizeof (userTime), requester );
516    
517     /* Get the minimum and maximum working set size for the current process */
518     GetProcessWorkingSetSize (handle, &minimumWorkingSetSize,
519     &maximumWorkingSetSize);
520     (*add) ( &minimumWorkingSetSize,
521     sizeof (&minimumWorkingSetSize), requester );
522     (*add) ( &maximumWorkingSetSize,
523     sizeof (&maximumWorkingSetSize), requester );
524     }
525    
526    
527     /* The following are fixed for the lifetime of the process so we only
528     * add them once */
529     if (!addedFixedItems) {
530     STARTUPINFO startupInfo;
531    
532     /* Get name of desktop, console window title, new window position and
533     * size, window flags, and handles for stdin, stdout, and stderr */
534     startupInfo.cb = sizeof (STARTUPINFO);
535     GetStartupInfo (&startupInfo);
536     (*add) ( &startupInfo, sizeof (STARTUPINFO), requester );
537     addedFixedItems = 1;
538     }
539    
540     /* The performance of QPC varies depending on the architecture it's
541     * running on and on the OS. Under NT it reads the CPU's 64-bit timestamp
542     * counter (at least on a Pentium and newer '486's, it hasn't been tested
543     * on anything without a TSC), under Win95 it reads the 1.193180 MHz PIC
544     * timer. There are vague mumblings in the docs that it may fail if the
545     * appropriate hardware isn't available (possibly '386's or MIPS machines
546     * running NT), but who's going to run NT on a '386? */
547     { LARGE_INTEGER performanceCount;
548     if (QueryPerformanceCounter (&performanceCount)) {
549     (*add) (&performanceCount, sizeof (&performanceCount), requester);
550     }
551     else { /* Millisecond accuracy at best... */
552     DWORD aword = GetTickCount ();
553     (*add) (&aword, sizeof (aword), requester );
554     }
555     }
556    
557     return 0;
558     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26