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

Contents of /trunk/Gnupg/rndw32.c

Parent Directory Parent Directory | Revision Log Revision Log


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

Properties

Name Value
svn:eol-style native

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26