/[AnywhereTS-MSSQL]/trunk/TSAdminTool/TerminalServices.cs
ViewVC logotype

Contents of /trunk/TSAdminTool/TerminalServices.cs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4 - (show annotations) (download)
Wed Jul 11 14:19:13 2012 UTC (7 years, 7 months ago) by william
File size: 28041 byte(s)
imported from https://anywherets.svn.sourceforge.net/svnroot/anywherets/trunk/
Revision: 1
Author: beartown
Date: 3:10:40 AM, Monday, June 21, 2010
Message:
First upload. Still todo in order to get a fully working product: Replace Thinstation images with vanilla Thinstation, modify ImageDesigntimeConfig.cs, ImageRuntimeConfig.cs in order to support the Thinstation image
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Runtime.InteropServices;
5 using System.Net;
6 using System.Windows.Forms;
7 using System.Data;
8
9 namespace AnywhereTS
10 {
11 public class TSManager
12 {
13 // Imports
14 [DllImport("wtsapi32.dll")]
15 static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);
16
17 [DllImport("wtsapi32.dll")]
18 public static extern void WTSCloseServer(IntPtr hServer);
19
20 [DllImport("wtsapi32.dll")]
21 public static extern Int32 WTSEnumerateSessions(
22 IntPtr hServer,
23 [MarshalAs(UnmanagedType.U4)] Int32 Reserved,
24 [MarshalAs(UnmanagedType.U4)] Int32 Version,
25 ref IntPtr ppSessionInfo,
26 [MarshalAs(UnmanagedType.U4)] ref Int32 pCount);
27
28 [DllImport("wtsapi32.dll", EntryPoint = "WTSQuerySessionInformation", CallingConvention = CallingConvention.Cdecl)]
29 public static extern bool WTSQuerySessionInformation(
30 System.IntPtr hServer,
31 uint sessionId,
32 WTSInfoClass wtsInfoClass,
33 out System.IntPtr ppBuffer,
34 out uint pBytesReturned);
35
36 [DllImport("wtsapi32.dll")]
37 static extern void WTSFreeMemory(IntPtr pMemory);
38
39 // For Lookup of MAC address
40 [DllImport("iphlpapi.dll", ExactSpelling = true)]
41 public static extern int SendARP(int DestIP, int SrcIP, [Out] byte[]
42 pMacAddr, ref int PhyAddrLen);
43
44 // Consts
45 public const uint WTS_CURRENT_SESSION = 4294967295; // = -1
46
47 // Type definitions
48
49 [StructLayout(LayoutKind.Sequential)]
50
51 private struct WTS_SESSION_INFO
52 {
53 public uint SessionID;
54
55 [MarshalAs(UnmanagedType.LPStr)]
56 public String pWinStationName;
57
58 public WTS_CONNECTSTATE_CLASS State;
59 }
60
61 public enum WTS_CONNECTSTATE_CLASS
62 {
63 WTSActive, // User logged on to WinStation
64 WTSConnected, // WinStation connected to client
65 WTSConnectQuery, // In the process of connecting to client
66 WTSShadow, // Shadowing another WinStation
67 WTSDisconnected, // WinStation logged on without client
68 WTSIdle, // Waiting for client to connect
69 WTSListen, // WinStation is listening for connection
70 WTSReset, // WinStation is being reset
71 WTSDown, // WinStation is down due to error
72 WTSInit, // WinStation in initialization
73 };
74
75 public enum WTSInfoClass
76 {
77 WTSInitialProgram,
78 WTSApplicationName,
79 WTSWorkingDirectory,
80 WTSOEMId,
81 WTSSessionId,
82 WTSUserName,
83 WTSWinStationName,
84 WTSDomainName,
85 WTSConnectState,
86 WTSClientBuildNumber,
87 WTSClientName,
88 WTSClientDirectory,
89 WTSClientProductId,
90 WTSClientHardwareId,
91 WTSClientAddress,
92 WTSClientDisplay,
93 WTSClientProtocolType,
94 } ;
95
96 public struct WTS_CLIENT_ADDRESS
97 {
98 public uint AddressFamily; // AF_INET, AF_IPX, AF_NETBIOS, AF_UNSPEC
99 public Byte[] Address;
100 };
101
102 public struct WTS_CLIENT_DISPLAY
103 {
104 public uint HorizontalResolution; // horizontal dimensions, in pixels
105 public uint VerticalResolution; // vertical dimensions, in pixels
106 public uint ColorDepth; // 1=16, 2=256, 4=64K, 8=16M
107 };
108
109
110 // Selected info for terminal server sessions
111 public struct TS_SESSION_INFO
112 {
113 public uint sessionID;
114 public string ipAddress; // The IP address of the client
115 public string macAddress; // The MAC address of the client
116 public string name; // The (netbios) name of the client
117 public string username; // User logged in to the session, if any.
118 public WTS_CONNECTSTATE_CLASS state; // The state of the session
119 public int HorizontalResolution; // Vertical screen res for the session
120 public int VerticalResolution; // Horiz screen res for the session
121 public int ColorDepth; // Screen color depth for the session
122 };
123
124
125 public static IntPtr OpenServer(String Name)
126 {
127 IntPtr server = WTSOpenServer(Name);
128 return server;
129 }
130 public static void CloseServer(IntPtr ServerHandle)
131 {
132 WTSCloseServer(ServerHandle);
133 }
134
135
136 // Return address for a terminal server session.
137 // WTSClientAddress gives a pointer to a WTS_CLIENT_ADDRESS structure containing
138 // the network type and network address of the client. If the function is called
139 // from the Terminal Services console, ppBuffer returns a NULL pointer.
140 // Note that the first byte of the IP address returned in the ppBuffer
141 // parameter will be located at an offset of two bytes from the first location
142 // of the buffer.
143 private static string GetTSClientAddress(uint sessionID, IntPtr server)
144 {
145 System.IntPtr ppBuffer = System.IntPtr.Zero;
146 uint pBytesReturned = 0;
147 StringBuilder builder = new StringBuilder();
148
149 // Interface avec API
150 WTS_CLIENT_ADDRESS wtsAdr = new WTS_CLIENT_ADDRESS();
151
152
153 if (WTSQuerySessionInformation(
154 server,
155 sessionID,
156 WTSInfoClass.WTSClientAddress,
157 out ppBuffer,
158 out pBytesReturned))
159 {
160 wtsAdr.Address = new Byte[pBytesReturned - 1];
161 int run = (int)ppBuffer; // pointeur sur les données
162 Type t = typeof(Byte);
163 Type t1 = typeof(uint);
164
165 int uintSize = Marshal.SizeOf(t1);
166 int byteSize = Marshal.SizeOf(t);
167
168 wtsAdr.AddressFamily = (uint)Marshal.ReadInt32((System.IntPtr)run);
169
170 // Address family can be only:
171 // AF_UNSPEC = 0 (unspecified)
172 // AF_INET = 2 (internetwork: UDP, TCP, etc.)
173 // AF_IPX = AF_NS = 6 (IPX protocols: IPX, SPX, etc.)
174 // AF_NETBIOS = 17 (NetBios-style addresses)
175
176 //run+=uintSize;
177 //run+=dataSize;
178 /*switch(wtsAdr.AddressFamily)
179 {
180 case 0:builder.Append("AF_UNSPEC");
181 break;
182 case 1:builder.Append("AF_INET");
183 break;
184 case 2:builder.Append("AF_IPX");
185 break;
186 case 3:builder.Append("AF_NETBIOS");
187 break;
188 }*/
189 for (int i = 0; i < pBytesReturned - 1; i++)
190 {
191 wtsAdr.Address[i] = Marshal.ReadByte((System.IntPtr)run);
192 run += byteSize;
193 // TO GET and to SEE ALL the DATA
194 //builder.Append(wtsAdr.Address[i].ToString()+"-");
195 }
196 //builder.Append("-");
197
198 // The IP address is located in bytes 2, 3, 4, and 5. The other bytes are not used.
199 // If AddressFamily returns AF_UNSPEC, the first byte in Address
200 // is initialized to zero.
201
202 // Check if the returned address is an IP address
203 if (wtsAdr.AddressFamily == 2)
204 { // It is an IP address
205 builder.Append((wtsAdr.Address[4 + 2]).ToString());
206 builder.Append(".");
207 builder.Append((wtsAdr.Address[4 + 3]).ToString());
208 builder.Append(".");
209 builder.Append((wtsAdr.Address[4 + 4]).ToString());
210 builder.Append(".");
211 builder.Append((wtsAdr.Address[4 + 5]).ToString());
212 }
213 }
214 WTSFreeMemory(ppBuffer);
215 return builder.ToString();
216 }
217
218 // Get display parameters for a user session on a terminal server
219 // In: SessionID = identifier for the session
220 // In: server = server handle for the server that the session resides on.
221 private static void GetTSClientDisplay(uint sessionID, IntPtr server, out int horizontalResolution, out int verticalResolution, out int colorDepth)
222 {
223 horizontalResolution = 0;
224 verticalResolution = 0;
225 colorDepth = 0;
226
227 System.IntPtr ppBuffer = System.IntPtr.Zero;
228 uint pBytesReturned = 0;
229 StringBuilder sDisplay = new StringBuilder();
230
231 WTS_CLIENT_DISPLAY clientDisplay = new WTS_CLIENT_DISPLAY();
232 clientDisplay.HorizontalResolution = 0;
233 clientDisplay.VerticalResolution = 0;
234 clientDisplay.ColorDepth = 0;
235
236 Type dataType = typeof(WTS_CLIENT_DISPLAY);
237
238 if (WTSQuerySessionInformation(
239 server,
240 sessionID,
241 WTSInfoClass.WTSClientDisplay,
242 out ppBuffer,
243 out pBytesReturned))
244 {
245 clientDisplay = (WTS_CLIENT_DISPLAY)Marshal.PtrToStructure(ppBuffer, dataType);
246 horizontalResolution = (int)(clientDisplay.HorizontalResolution);
247 verticalResolution = (int)(clientDisplay.VerticalResolution);
248 colorDepth = (int)(clientDisplay.ColorDepth);
249 }
250
251 WTSFreeMemory(ppBuffer);
252 }
253
254
255 // Get Mac from IP, using ARP
256 private static string GetMACFromIP(string ipString)
257 {
258
259 IPAddress ip = IPAddress.Parse(ipString); // Actual IP
260 int rv;
261 string macStr;
262 byte[] mac = new byte[6];
263 int maclen = mac.Length;
264
265 rv = SendARP(BitConverter.ToInt32(ip.GetAddressBytes(), 0), 0, mac, ref maclen);
266 if (rv == 0) // If not 0, error
267 {
268 // macStr = BitConverter.ToString(mac, 0, 6);
269 macStr = BitConverter.ToString(mac, 0, 1)+BitConverter.ToString(mac, 1, 1)+BitConverter.ToString(mac, 2, 1)
270 +BitConverter.ToString(mac, 3, 1)+BitConverter.ToString(mac, 4, 1)+BitConverter.ToString(mac, 5, 1);
271 }
272 else
273 {
274 macStr = "";
275 }
276 return macStr;
277 }
278
279
280 private static string GetTSUserName(uint sessionID, IntPtr server)
281 {
282 System.IntPtr ppBuffer = System.IntPtr.Zero;
283 uint pBytesReturned = 0;
284 string currentUserName = "";
285
286 if (WTSQuerySessionInformation(
287 server,
288 sessionID,
289 WTSInfoClass.WTSUserName,
290 out ppBuffer,
291 out pBytesReturned))
292 {
293 currentUserName = Marshal.PtrToStringAnsi(ppBuffer);
294 }
295
296 WTSFreeMemory(ppBuffer);
297
298 return currentUserName;
299 }
300
301 public static string GetTSClientName(uint sessionID, IntPtr server)
302 {
303 System.IntPtr ppBuffer = System.IntPtr.Zero;
304 int adrBuffer;
305 uint pBytesReturned = 0;
306 string clientName = "";
307
308 if (WTSQuerySessionInformation(
309 server,
310 sessionID,
311 WTSInfoClass.WTSClientName,
312 out ppBuffer,
313 out pBytesReturned))
314 {
315 adrBuffer = (int)ppBuffer;
316 clientName = Marshal.PtrToStringAnsi((System.IntPtr)adrBuffer);
317 }
318
319 WTSFreeMemory(ppBuffer);
320
321 return clientName;
322 }
323
324 // List all sessions on all registered terminal servers and return in a list with selected data for each session.
325 public static List<TS_SESSION_INFO> ListSessions()
326 {
327 List<TS_SESSION_INFO> ret = new List<TS_SESSION_INFO>();
328 if (ATSGlobals.terminalServerConfig == 0) // This server is a terminal server
329 {
330 ListSessions(ret, "localhost"); // Add sessions from local server
331 }
332
333 // Add sessions from other terminal servers
334 atsDataSet.TerminalServerDataTable datatableTerminalServer;
335 datatableTerminalServer = new atsDataSet.TerminalServerDataTable();
336 ProSupport.terminalServerTableAdapter.Fill(datatableTerminalServer);
337 foreach (DataRow row in datatableTerminalServer.Rows)
338 {
339 string strError = "";
340 try
341 {
342 strError = ListSessions(ret, row["Path"].ToString());
343 }
344 catch
345 {
346 //MessageBox.Show("Error: Could not retrieve session data from server '" + row["Path"].ToString() + "'. Error 39092.");
347 }
348 if (strError.Length != 0)
349 {
350 //MessageBox.Show("Warning: " + strError, "AnywhereTS");
351 }
352 }
353 return ret;
354 }
355
356 // List all sessions on the terminal server and return in a list with selected data for each session.
357 public static List<TS_SESSION_INFO> ListSessions(String serverName)
358 {
359 IntPtr server = IntPtr.Zero;
360 List<TS_SESSION_INFO> ret = new List<TS_SESSION_INFO>();
361 server = OpenServer(serverName);
362
363 try
364 {
365 IntPtr ppSessionInfo = IntPtr.Zero;
366
367 Int32 count = 0;
368 Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count);
369 Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
370 Int32 current = (int)ppSessionInfo;
371 TS_SESSION_INFO CurrentClientInfo;
372
373 if (retval != 0)
374 {
375 for (int i = 0; i < count; i++) // Iterate through all sessions on the server
376 {
377 WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
378 current += dataSize;
379 CurrentClientInfo.sessionID = si.SessionID;
380 CurrentClientInfo.ipAddress = GetTSClientAddress(si.SessionID, server);
381 CurrentClientInfo.name = GetTSClientName(si.SessionID, server);
382 CurrentClientInfo.username = GetTSUserName(si.SessionID, server);
383 CurrentClientInfo.state = si.State;
384 if (CurrentClientInfo.name != "")
385 {
386 CurrentClientInfo.macAddress = ProSupport.GetMacAddressFromNameAndDatabase(CurrentClientInfo.name);
387 }
388 else
389 {
390 CurrentClientInfo.macAddress = "";
391 }
392
393 GetTSClientDisplay(
394 si.SessionID,
395 server,
396 out CurrentClientInfo.HorizontalResolution,
397 out CurrentClientInfo.VerticalResolution,
398 out CurrentClientInfo.ColorDepth);
399 ret.Add(CurrentClientInfo);
400 }
401
402 WTSFreeMemory(ppSessionInfo);
403 }
404 }
405
406 finally
407 {
408 try
409 {
410 CloseServer(server);
411 }
412 catch
413 {
414 MessageBox.Show("Error could not close terminal server connection (63210)");
415 }
416 }
417 return ret;
418 }
419
420 // List all sessions on the terminal server and return in a list with selected data for each session.
421 public static List<AtsSession> ListTSsessions(String serverName)
422 {
423 IntPtr server = IntPtr.Zero;
424 List<AtsSession> ret = new List<AtsSession>();
425 server = OpenServer(serverName);
426
427 try
428 {
429 IntPtr ppSessionInfo = IntPtr.Zero;
430
431 Int32 count = 0;
432 Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count);
433 Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
434 Int32 current = (int)ppSessionInfo;
435 AtsSession CurrentSession = new AtsSession();
436
437 if (retval != 0)
438 {
439 for (int i = 0; i < count; i++) // Iterate through all sessions on the server
440 {
441 WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
442 current += dataSize;
443 CurrentSession.sessionID = si.SessionID;
444 CurrentSession.ipAddress = GetTSClientAddress(si.SessionID, server);
445 CurrentSession.name = GetTSClientName(si.SessionID, server);
446 CurrentSession.username = GetTSUserName(si.SessionID, server);
447 CurrentSession.state = si.State;
448 if (CurrentSession.name != "")
449 {
450 CurrentSession.macAddress = ProSupport.GetMacAddressFromNameAndDatabase(CurrentSession.name);
451 }
452 else
453 {
454 CurrentSession.macAddress = "";
455 }
456
457 GetTSClientDisplay(
458 si.SessionID,
459 server,
460 out CurrentSession.HorizontalResolution,
461 out CurrentSession.VerticalResolution,
462 out CurrentSession.ColorDepth);
463 ret.Add(CurrentSession);
464 }
465
466 WTSFreeMemory(ppSessionInfo);
467 }
468 }
469
470 finally
471 {
472 try
473 {
474 CloseServer(server);
475 }
476 catch
477 {
478 MessageBox.Show("Error could not close terminal server connection (63210)");
479 }
480 }
481 return ret;
482 }
483
484
485 // List all sessions on the terminal server and adds them to list with selected data for each session.
486 // Returns error message or "" if everything went ok.
487 public static string ListSessions(List<TS_SESSION_INFO> SessionInfo, String serverName)
488 {
489 IntPtr server = IntPtr.Zero;
490 server = OpenServer(serverName);
491 string strError = "Could not retrieve session data from server '" + serverName + "'"; // Assume something went wrong
492 if (server != IntPtr.Zero)
493 {
494 try
495 {
496 IntPtr ppSessionInfo = IntPtr.Zero;
497
498 Int32 count = 0;
499 Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count);
500 Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
501 Int32 current = (int)ppSessionInfo;
502 TS_SESSION_INFO CurrentClientInfo;
503
504 if (retval != 0)
505 {
506 for (int i = 0; i < count; i++)
507 {
508 WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
509 current += dataSize;
510 CurrentClientInfo.sessionID = si.SessionID;
511 CurrentClientInfo.ipAddress = GetTSClientAddress(si.SessionID, server);
512 CurrentClientInfo.name = GetTSClientName(si.SessionID, server);
513 CurrentClientInfo.username = GetTSUserName(si.SessionID, server);
514 CurrentClientInfo.state = si.State;
515 if (CurrentClientInfo.name != "")
516 {
517 CurrentClientInfo.macAddress = ProSupport.GetMacAddressFromNameAndDatabase(CurrentClientInfo.name);
518 }
519 else
520 {
521 CurrentClientInfo.macAddress = "";
522 }
523 GetTSClientDisplay(
524 si.SessionID,
525 server,
526 out CurrentClientInfo.HorizontalResolution,
527 out CurrentClientInfo.VerticalResolution,
528 out CurrentClientInfo.ColorDepth);
529
530 SessionInfo.Add(CurrentClientInfo);
531 }
532
533 WTSFreeMemory(ppSessionInfo);
534 }
535 }
536
537 finally
538 {
539 try
540 {
541 CloseServer(server);
542 }
543 finally
544 {
545 // Catch exeception
546 }
547 }
548 strError = ""; // Everything went ok
549 } // end if (server != null)
550 return strError;
551 }
552
553 // List all sessions on the terminal server and adds them to list with selected data for each session.
554 // Returns error message or "" if everything went ok.
555 public static string ListSessions(List<AtsSession> SessionInfo, String serverName)
556 {
557 IntPtr server = IntPtr.Zero;
558 server = OpenServer(serverName);
559 string strError = "Could not retrieve session data from server '" + serverName + "'"; // Assume something went wrong
560 if (server != IntPtr.Zero)
561 {
562 try
563 {
564 IntPtr ppSessionInfo = IntPtr.Zero;
565
566 Int32 count = 0;
567 Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count);
568 Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
569 Int32 current = (int)ppSessionInfo;
570
571 if (retval != 0)
572 {
573 for (int i = 0; i < count; i++)
574 {
575 AtsSession currentSession = new AtsSession();
576 WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
577 current += dataSize;
578 currentSession.TerminalServerName = serverName;
579 currentSession.sessionID = si.SessionID;
580 currentSession.ipAddress = GetTSClientAddress(si.SessionID, server);
581 currentSession.name = GetTSClientName(si.SessionID, server);
582 currentSession.username = GetTSUserName(si.SessionID, server);
583 currentSession.state = si.State;
584 if (currentSession.name != "")
585 {
586 currentSession.macAddress = ProSupport.GetMacAddressFromNameAndDatabase(currentSession.name);
587 }
588 else
589 {
590 currentSession.macAddress = "";
591 }
592 GetTSClientDisplay(
593 si.SessionID,
594 server,
595 out currentSession.HorizontalResolution,
596 out currentSession.VerticalResolution,
597 out currentSession.ColorDepth);
598
599 SessionInfo.Add(currentSession);
600 }
601
602 WTSFreeMemory(ppSessionInfo);
603 }
604 }
605 finally
606 {
607 try
608 {
609 CloseServer(server);
610 }
611 finally
612 {
613 // Catch exeception
614 }
615 }
616 strError = ""; // Everything went ok
617 } // end if (server != null)
618
619 return strError;
620 }
621
622 // Return matching session for a macAddress, or null, if the mac address is not used in any session.
623 // Called when a child node selected in the tree view
624 public static AtsSession GetSession(string macAddress)
625 {
626 foreach (AtsTerminalServer ts in AtsEnvironment.TerminalServers)
627 {
628 foreach (AtsSession session in ts.Session)
629 {
630 // Try to find the MAC adress for the selected client in any of the sessions.
631 if (session.macAddress == macAddress)
632 {
633 return session;
634 }
635 }
636 }
637 // Session not found, return null.
638 return null;
639 }
640
641 // Convert session state to string
642 public static string StateToString(WTS_CONNECTSTATE_CLASS state)
643 {
644 switch (state)
645 {
646 case WTS_CONNECTSTATE_CLASS.WTSActive:
647 return "User logged on";
648 case WTS_CONNECTSTATE_CLASS.WTSConnected:
649 return "Connected to client";
650 case WTS_CONNECTSTATE_CLASS.WTSConnectQuery:
651 return "Connecting to client";
652 case WTS_CONNECTSTATE_CLASS.WTSShadow:
653 return "Shadowing";
654 case WTS_CONNECTSTATE_CLASS.WTSDisconnected:
655 return "Logged on without client";
656 case WTS_CONNECTSTATE_CLASS.WTSIdle:
657 return "Waiting for client to connect";
658 case WTS_CONNECTSTATE_CLASS.WTSListen:
659 return "Listening for connection";
660 case WTS_CONNECTSTATE_CLASS.WTSReset:
661 return "Being reset";
662 case WTS_CONNECTSTATE_CLASS.WTSDown:
663 return "Down due to error";
664 case WTS_CONNECTSTATE_CLASS.WTSInit:
665 return "In initialization";
666 default:
667 return "Unknown state (error!)";
668 }
669 }
670 public static string GetMyName()
671 {
672 return GetTSClientName(WTS_CURRENT_SESSION, System.IntPtr.Zero);
673 }
674
675 } // Class TSManager
676 } // Namespace TerminalServices

  ViewVC Help
Powered by ViewVC 1.1.22