(file) Return to snmpIndicationHandler.cpp CVS log (file) (dir) Up to [Pegasus] / pegasus / src / Pegasus / Handler / snmpIndicationHandler

  1 karl  1.29 //%2006////////////////////////////////////////////////////////////////////////
  2 mike  1.2  //
  3 karl  1.24 // Copyright (c) 2000, 2001, 2002 BMC Software; Hewlett-Packard Development
  4            // Company, L.P.; IBM Corp.; The Open Group; Tivoli Systems.
  5            // Copyright (c) 2003 BMC Software; Hewlett-Packard Development Company, L.P.;
  6 karl  1.21 // IBM Corp.; EMC Corporation, The Open Group.
  7 karl  1.24 // Copyright (c) 2004 BMC Software; Hewlett-Packard Development Company, L.P.;
  8            // IBM Corp.; EMC Corporation; VERITAS Software Corporation; The Open Group.
  9 karl  1.25 // Copyright (c) 2005 Hewlett-Packard Development Company, L.P.; IBM Corp.;
 10            // EMC Corporation; VERITAS Software Corporation; The Open Group.
 11 karl  1.29 // Copyright (c) 2006 Hewlett-Packard Development Company, L.P.; IBM Corp.;
 12            // EMC Corporation; Symantec Corporation; The Open Group.
 13 mike  1.2  //
 14            // Permission is hereby granted, free of charge, to any person obtaining a copy
 15 kumpf 1.9  // of this software and associated documentation files (the "Software"), to
 16            // deal in the Software without restriction, including without limitation the
 17            // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 18 mike  1.2  // sell copies of the Software, and to permit persons to whom the Software is
 19            // furnished to do so, subject to the following conditions:
 20            // 
 21 kumpf 1.9  // THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN
 22 mike  1.2  // ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED
 23            // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 24 kumpf 1.9  // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 25            // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 26            // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 27 mike  1.2  // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 28            // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 29            //
 30            //==============================================================================
 31            //
 32            //%/////////////////////////////////////////////////////////////////////////////
 33            
 34            #include <Pegasus/Common/Config.h>
 35 kumpf 1.7  #include <Pegasus/Common/PegasusVersion.h>
 36            
 37 mike  1.2  #include <iostream>
 38            #include <Pegasus/Handler/CIMHandler.h>
 39            #include <Pegasus/Repository/CIMRepository.h>
 40 kumpf 1.23 #include <Pegasus/Common/Tracer.h>
 41 mike  1.2  
 42            #include "snmpIndicationHandler.h"
 43            
 44            #ifdef HPUX_EMANATE
 45 kumpf 1.34 # include "snmpDeliverTrap_emanate.h"
 46 yi.zhou 1.27 #elif defined (PEGASUS_USE_NET_SNMP)
 47 kumpf   1.34 # include "snmpDeliverTrap_netsnmp.h"
 48 mike    1.2  #else
 49 kumpf   1.34 # include "snmpDeliverTrap_stub.h"
 50 mike    1.2  #endif
 51              
 52 humberto 1.15 #include <Pegasus/Common/MessageLoader.h>
 53               
 54 kumpf    1.32 PEGASUS_USING_STD;
 55               
 56 mike     1.2  PEGASUS_NAMESPACE_BEGIN
 57               
 58 kumpf    1.32 snmpIndicationHandler::snmpIndicationHandler()
 59               {
 60                   PEG_METHOD_ENTER (TRC_IND_HANDLER,
 61                       "snmpIndicationHandler::snmpIndicationHandler");
 62 mike     1.2  
 63 yi.zhou  1.30 #ifdef HPUX_EMANATE
 64 kumpf    1.32     _snmpTrapSender = new snmpDeliverTrap_emanate();
 65 yi.zhou  1.30 #elif defined (PEGASUS_USE_NET_SNMP)
 66 kumpf    1.32     _snmpTrapSender = new snmpDeliverTrap_netsnmp();
 67 yi.zhou  1.30 #else
 68 kumpf    1.32     _snmpTrapSender = new snmpDeliverTrap_stub();
 69 yi.zhou  1.30 #endif
 70               
 71                   PEG_METHOD_EXIT();
 72               }
 73               
 74 mike     1.2  void snmpIndicationHandler::initialize(CIMRepository* repository)
 75               {
 76 yi.zhou  1.30     PEG_METHOD_ENTER (TRC_IND_HANDLER,
 77                       "snmpIndicationHandler::initialize");
 78               
 79 mike     1.2      _repository = repository;
 80 yi.zhou  1.30 
 81 kumpf    1.32     _snmpTrapSender->initialize();
 82 yi.zhou  1.30 
 83                   PEG_METHOD_EXIT();
 84               }
 85               
 86               void snmpIndicationHandler::terminate()
 87               {
 88 kumpf    1.34     PEG_METHOD_ENTER(TRC_IND_HANDLER,
 89 yi.zhou  1.30         "snmpIndicationHandler::terminate");
 90               
 91 kumpf    1.32     _snmpTrapSender->terminate();
 92 yi.zhou  1.30 
 93                   PEG_METHOD_EXIT();
 94               }
 95               
 96               snmpIndicationHandler::~snmpIndicationHandler()
 97               {
 98 kumpf    1.34     PEG_METHOD_ENTER(TRC_IND_HANDLER,
 99 yi.zhou  1.30         "snmpIndicationHandler::~snmpIndicationHandler");
100               
101 kumpf    1.32     delete _snmpTrapSender;
102 yi.zhou  1.30 
103                   PEG_METHOD_EXIT();
104 mike     1.2  }
105               
106 chuck    1.14 // l10n - note: ignoring indication language
107 kumpf    1.16 void snmpIndicationHandler::handleIndication(
108                   const OperationContext& context,
109 yi.zhou  1.26     const String nameSpace,
110                   CIMInstance& indication,
111 kumpf    1.34     CIMInstance& handler,
112 yi.zhou  1.26     CIMInstance& subscription,
113 kumpf    1.28     ContentLanguageList & contentLanguages)
114 mike     1.2  {
115                   Array<String> propOIDs;
116                   Array<String> propTYPEs;
117                   Array<String> propVALUEs;
118               
119 yi.zhou  1.36     Array<String> mapStr;
120 mike     1.2  
121 kumpf    1.34     PEG_METHOD_ENTER(TRC_IND_HANDLER,
122                       "snmpIndicationHandler::handleIndication");
123 mike     1.2  
124 kumpf    1.20     try
125                   {
126 mike     1.37.16.1         PEG_TRACE ((TRC_INDICATION_GENERATION, Tracer::LEVEL4,
127 w.otsuka 1.37                  "snmpIndicationHandler %s:%s.%s processing %s Indication",
128                               (const char*)(nameSpace.getCString()),
129                               (const char*)(handler.getClassName().getString().getCString()),
130                               (const char*)(handler.getProperty(
131                               handler.findProperty(PEGASUS_PROPERTYNAME_NAME)).
132                               getValue().toString().getCString()),
133                               (const char*)(indication.getClassName().getString().
134                               getCString())));
135 kumpf    1.34              CIMClass indicationClass = _repository->getClass(
136                                nameSpace, indication.getClassName(), false, true,
137                                false, CIMPropertyList());
138 kumpf    1.20      
139 kumpf    1.34              Uint32 propertyCount = indication.getPropertyCount();
140 kumpf    1.13      
141 kumpf    1.34              for (Uint32 i=0; i < propertyCount; i++)
142                            {
143 yi.zhou  1.36                  CIMProperty prop = indication.getProperty(i);
144 mike     1.2       
145 yi.zhou  1.36                  Uint32 propDeclPos = indicationClass.findProperty(prop.getName());
146                                if (propDeclPos != PEG_NOT_FOUND)
147 kumpf    1.4                   {
148 yi.zhou  1.36                      CIMProperty propDecl = indicationClass.getProperty(propDeclPos);
149                    
150                                    Uint32 qualifierPos =
151                                        propDecl.findQualifier(CIMName("MappingStrings"));
152                                    if (qualifierPos != PEG_NOT_FOUND)
153 kumpf    1.20                      {
154 yi.zhou  1.36                          //
155                                        // We are looking for following fields:
156                                        // MappingStrings {"OID.IETF | SNMP." oidStr, 
157                                        //     "DataType.IETF |" dataType}
158                                        // oidStr is the object identifier (e.g. "1.3.6.1.2.1.5..."
159                                        // dataType is either Integer, or OctetString, 
160                                        // or OID
161                                        // Following is one example:
162                                        // MappingStrings {"OID.IETF | SNMP.1.3.6.6.3.1.1.5.2",
163                                        //    "DataType.IETF | Integer"}
164                                        //
165                    
166                                        propDecl.getQualifier(qualifierPos).getValue().get(
167                                            mapStr);
168                     
169                                        String oidStr, dataType;
170                                        String mapStr1, mapStr2;
171                                        Boolean isValidAuthority = false;
172                                        Boolean isValidDataType = false;
173 mike     1.2       
174 yi.zhou  1.36                          for (Uint32 j=0; j < mapStr.size(); j++)
175 kumpf    1.20                          {
176 yi.zhou  1.36                              Uint32 barPos = mapStr[j].find("|");
177                                                
178                                            if (barPos != PEG_NOT_FOUND) 
179                                            {
180                                                mapStr1 = mapStr[j].subString(0, barPos);
181                                                mapStr2 = mapStr[j].subString(barPos + 1);
182 kumpf    1.34      
183 yi.zhou  1.36                                  _trimWhitespace(mapStr1);
184                                                _trimWhitespace(mapStr2);
185                                                    
186                                                if ((mapStr1 == "OID.IETF") &&
187                                                    (String::compare(mapStr2, 
188                                                     String("SNMP."), 5) == 0))
189                                                {
190                                                    isValidAuthority = true;
191                                                    oidStr = mapStr2.subString(5);
192                                                }
193                                                else if (mapStr1 == "DataType.IETF")
194                                                {
195                                                    isValidDataType = true;
196                                                    dataType = mapStr2;
197                                                }
198 kumpf    1.34      
199 yi.zhou  1.36                                  if (isValidAuthority && isValidDataType) 
200 kumpf    1.34                                  {
201 yi.zhou  1.36                                      propOIDs.append(oidStr);
202                                                    propTYPEs.append(dataType);
203                                                    propVALUEs.append(prop.getValue().toString());
204                    
205                                                    break;
206 kumpf    1.34                                  }
207                                            }
208                                        }
209 kumpf    1.20                      }
210 kumpf    1.4                   }
211 mike     1.2               }
212                    
213                            // Collected complete data in arrays and ready to send the trap.
214                            // trap destination and SNMP type are defined in handlerInstance
215                            // and passing this instance as it is to deliverTrap() call
216                    
217 kumpf    1.34              Uint32 targetHostPos = handler.findProperty(CIMName("TargetHost"));
218                            Uint32 targetHostFormatPos =
219                                handler.findProperty(CIMName("TargetHostFormat"));
220                            Uint32 otherTargetHostFormatPos =
221                                handler.findProperty(CIMName("OtherTargetHostFormat"));
222                            Uint32 portNumberPos = handler.findProperty(CIMName("PortNumber"));
223                            Uint32 snmpVersionPos = handler.findProperty(CIMName("SNMPVersion"));
224                            Uint32 securityNamePos =
225                                handler.findProperty(CIMName("SNMPSecurityName"));
226                            Uint32 engineIDPos = handler.findProperty(CIMName("SNMPEngineID"));
227 kumpf    1.20      
228 w.otsuka 1.37              if (targetHostPos == PEG_NOT_FOUND)
229                            {
230 mike     1.37.16.1             PEG_TRACE((TRC_DISCARDED_DATA, Tracer::LEVEL1,
231 w.otsuka 1.37                      "Target host is not set for IndicationHandlerSNMPMapper %s"
232                                    " Indication.",
233                                    (const char*)(indication.getClassName().getString().
234                                    getCString())));
235                                PEG_METHOD_EXIT();
236                                throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
237                                    "Handler.snmpIndicationHandler.snmpIndicationHandler."
238                                    "INVALID_SNMP_INSTANCE",
239                                    "Invalid IndicationHandlerSNMPMapper instance"));
240                            }
241                            if (targetHostFormatPos == PEG_NOT_FOUND)
242                            {
243 mike     1.37.16.1             PEG_TRACE((TRC_DISCARDED_DATA, Tracer::LEVEL1,
244 w.otsuka 1.37                      "Target host format is not set for IndicationHandlerSNMPMapper"
245                                    " %s Indication.",
246                                    (const char*)(indication.getClassName().getString().
247                                    getCString())));
248                                PEG_METHOD_EXIT();
249                                throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
250                                    "Handler.snmpIndicationHandler.snmpIndicationHandler."
251                                    "INVALID_SNMP_INSTANCE",
252                                    "Invalid IndicationHandlerSNMPMapper instance"));
253                            }
254                            if (snmpVersionPos == PEG_NOT_FOUND)
255                            {
256 mike     1.37.16.1             PEG_TRACE((TRC_DISCARDED_DATA, Tracer::LEVEL1,
257 w.otsuka 1.37                      "SNMP Version is not set for IndicationHandlerSNMPMapper %s"
258                                    " Indication.",
259                                    (const char*)(indication.getClassName().getString().
260                                    getCString())));
261                                PEG_METHOD_EXIT();
262                                throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
263                                    "Handler.snmpIndicationHandler.snmpIndicationHandler."
264                                    "INVALID_SNMP_INSTANCE",
265                                    "Invalid IndicationHandlerSNMPMapper instance"));
266                            }
267                            else
268 kumpf    1.16              {
269 kumpf    1.34                  // properties from the handler instance
270 kumpf    1.20                  String targetHost;
271 kumpf    1.34                  String otherTargetHostFormat = String();
272                                String securityName = String();
273                                String engineID = String();
274                                Uint16 targetHostFormat = 0;
275                                Uint16 snmpVersion = 0;
276                                Uint32 portNumber;
277 kumpf    1.20      
278 kumpf    1.34                  String trapOid;
279 yi.zhou  1.36                  Boolean trapOidAvailable = false;
280 kumpf    1.34                  //
281 kumpf    1.20                  //  Get snmpTrapOid from context
282                                //
283 kumpf    1.34                  if (context.contains(SnmpTrapOidContainer::NAME))
284                                {
285                                    SnmpTrapOidContainer trapContainer =
286                                        context.get(SnmpTrapOidContainer::NAME);
287 kumpf    1.16      
288 kumpf    1.20                      trapOid = trapContainer.getSnmpTrapOid();
289 yi.zhou  1.36                      trapOidAvailable = true;
290 kumpf    1.34                  }
291                                else
292 kumpf    1.20                  {
293 kumpf    1.34                      // get trapOid from indication Class
294 kumpf    1.16      
295 kumpf    1.34                      Uint32 pos =
296                                        indicationClass.findQualifier(CIMName("MappingStrings"));
297                                    if (pos != PEG_NOT_FOUND)
298                                    {
299 yi.zhou  1.36                          Array<String> classMapStr;
300                                        indicationClass.getQualifier(pos).getValue().
301                                            get(classMapStr);
302 kumpf    1.20      
303 yi.zhou  1.36                          for (Uint32 i=0; i < classMapStr.size(); i++)
304                                        {
305                                            Uint32 barPos = classMapStr[i].find("|");
306 kumpf    1.20      
307 yi.zhou  1.36                              if (barPos != PEG_NOT_FOUND) 
308                                            {
309                                                String authorityName = 
310                                                    classMapStr[i].subString(0, barPos);
311                                                String oidStr = classMapStr[i].subString(
312                                                    barPos+1, PEG_NOT_FOUND);
313                    
314                                                _trimWhitespace(authorityName);
315                                                _trimWhitespace(oidStr);
316                    
317                                                if ((authorityName == "OID.IETF") &&
318                                                    (String::compare(oidStr, 
319                                                     String("SNMP."), 5) == 0))
320                                                {
321                                                    trapOid = oidStr.subString(5); 
322                                                    trapOidAvailable = true;
323                                                    break;
324                                                }
325                                            }
326 kumpf    1.20                          }
327 yi.zhou  1.36      
328                                        if (!trapOidAvailable)
329 kumpf    1.34                          {
330 mike     1.37.16.1                         PEG_TRACE((
331                                                TRC_IND_HANDLER,
332                                                Tracer::LEVEL1,
333                                                "No MappingStrings for snmp trap is specified "
334                                                    "for class: %s",
335                                                (const char*)
336                                                  indication.getClassName().getString().getCString()
337                                                ));
338 yi.zhou  1.36      
339 kumpf    1.34                              PEG_METHOD_EXIT();
340 yi.zhou  1.36      
341 mike     1.37.16.1                         throw PEGASUS_CIM_EXCEPTION_L(
342                                                CIM_ERR_FAILED,
343 kumpf    1.34                                  MessageLoaderParms(
344                                                    "Handler.snmpIndicationHandler."
345 yi.zhou  1.36                                      "snmpIndicationHandler.NO_MS_FOR_SNMP_TRAP",
346 mike     1.37.16.1                                 "No MappingStrings for snmp trap is specified "
347                                                        "for class: $0",
348                                                    indication.getClassName().getString()));
349 kumpf    1.34                          }
350                                    }
351                                    else
352                                    {
353 mike     1.37.16.1                     PEG_TRACE_CSTRING(TRC_IND_HANDLER, Tracer::LEVEL1,
354 kumpf    1.34                              "Qualifier MappingStrings can not be found.");
355                                        PEG_METHOD_EXIT();
356                                        MessageLoaderParms parms(
357                                            "Handler.snmpIndicationHandler.snmpIndicationHandler."
358                                                "QUALIFIER_MAPPINGS_NOT_FOUND",
359                                            "Qualifier MappingStrings can not be found");
360                                        throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, parms);
361                                    }
362                                }
363                    
364                                handler.getProperty(targetHostPos).getValue().get(targetHost);
365                                handler.getProperty(targetHostFormatPos).getValue().get(
366                                    targetHostFormat);
367                                if (otherTargetHostFormatPos != PEG_NOT_FOUND)
368                                {
369                                    handler.getProperty(otherTargetHostFormatPos).getValue().get(
370                                        otherTargetHostFormat);
371                                }
372                                if (portNumberPos != PEG_NOT_FOUND)
373                                {
374                                    handler.getProperty(portNumberPos).getValue().get(portNumber);
375 kumpf    1.34                  }
376                                else
377                                {
378                                    // default port
379                                    portNumber = SNMP_TRAP_DEFAULT_PORT;
380                                }
381                    
382                                handler.getProperty(snmpVersionPos).getValue().get(snmpVersion);
383                                if (securityNamePos != PEG_NOT_FOUND)
384                                {
385                                    handler.getProperty(securityNamePos).getValue().get(
386                                        securityName);
387                                }
388                                if (engineIDPos != PEG_NOT_FOUND)
389                                {
390                                    handler.getProperty(engineIDPos).getValue().get(engineID);
391                                }
392 kumpf    1.13      
393 mike     1.37.16.1             PEG_TRACE ((TRC_INDICATION_GENERATION, Tracer::LEVEL4,
394 w.otsuka 1.37                     "snmpIndicationHandler sending %s Indication trap %s to target"
395                                   " host %s target port %d",
396                                   (const char*)(indication.getClassName().getString().
397                                   getCString()),
398                                   (const char*)(trapOid.getCString()),
399                                   (const char*)(targetHost.getCString()),portNumber));
400                               _snmpTrapSender->deliverTrap(
401 kumpf    1.20                      trapOid,
402                                    securityName,
403                                    targetHost,
404                                    targetHostFormat,
405 kumpf    1.34                      otherTargetHostFormat,
406                                    portNumber,
407                                    snmpVersion,
408                                    engineID,
409                                    propOIDs,
410                                    propTYPEs,
411 kumpf    1.20                      propVALUEs);
412 w.otsuka 1.37      
413 mike     1.37.16.1         PEG_TRACE ((TRC_INDICATION_GENERATION, Tracer::LEVEL4,
414 w.otsuka 1.37                 "%s Indication trap %s sent to target host %s target port %d "
415                               "successfully",
416                               (const char*)(indication.getClassName().getString().getCString()),
417                               (const char*)(trapOid.getCString()),
418                               (const char*)(targetHost.getCString()),portNumber));
419 kumpf    1.20              }
420 kumpf    1.34          }
421                        catch (CIMException& c)
422 kumpf    1.20          {
423 mike     1.37.16.1         PEG_TRACE_STRING(TRC_IND_HANDLER, Tracer::LEVEL1, c.getMessage());
424 kumpf    1.34              PEG_METHOD_EXIT();
425                            throw PEGASUS_CIM_EXCEPTION(CIM_ERR_FAILED, c.getMessage());
426 kumpf    1.20          }
427                        catch (Exception& e)
428                        {
429 mike     1.37.16.1         PEG_TRACE_STRING(TRC_IND_HANDLER, Tracer::LEVEL1, e.getMessage());
430 kumpf    1.34              PEG_METHOD_EXIT();
431 kumpf    1.20      
432 kumpf    1.34              throw PEGASUS_CIM_EXCEPTION(CIM_ERR_FAILED, e.getMessage());
433 kumpf    1.20          }
434                        catch (...)
435                        {
436 mike     1.37.16.1         PEG_TRACE_CSTRING(TRC_IND_HANDLER, Tracer::LEVEL1,
437 kumpf    1.34                  "Failed to deliver trap.");
438                            PEG_METHOD_EXIT();
439                    
440                            throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_FAILED, MessageLoaderParms(
441                                "Handler.snmpIndicationHandler.snmpIndicationHandler."
442                                    "FAILED_TO_DELIVER_TRAP",
443                                "Failed to deliver trap."));
444 kumpf    1.13          }
445 yi.zhou  1.30          PEG_METHOD_EXIT();
446 mike     1.2       }
447                    
448 yi.zhou  1.36      void snmpIndicationHandler::_trimWhitespace(
449                        String & nameStr)
450                    {
451                        PEG_METHOD_ENTER(TRC_IND_HANDLER,
452                            "snmpIndicationHandler::_trimWhitespace");
453                    
454                        Uint32 ps = 0;
455                        // skip begining whitespace
456                        for (ps = 0; ps < nameStr.size(); ps++)
457                        {
458                            if (nameStr[ps] != ' ')
459                            {
460                                break;
461                            }
462                        }
463                    
464                        if (ps != 0)
465                        {
466                            nameStr.remove(0, ps);
467                        }
468                    
469 yi.zhou  1.36          // skip the appended whitespace
470                        for (ps = nameStr.size(); ps != 0; ps--)
471                        {
472                            if (nameStr[ps-1] != ' ')
473                            {
474                                break;
475                            }
476                        }
477                    
478                        if (ps !=  nameStr.size())
479                        {
480                            nameStr.remove(ps);
481                        }
482                    
483                        PEG_METHOD_EXIT();
484                    }
485                    
486 kumpf    1.32      PEGASUS_NAMESPACE_END
487                    
488                    PEGASUS_USING_PEGASUS;
489                    
490 kumpf    1.33      // This is the entry point into this dynamic module.
491 mike     1.2       
492 kumpf    1.33      extern "C" PEGASUS_EXPORT CIMHandler* PegasusCreateHandler(
493                        const String& handlerName)
494 kumpf    1.32      {
495 kumpf    1.33          if (handlerName == "snmpIndicationHandler")
496                        {
497                            return new snmpIndicationHandler;
498                        }
499                    
500                        return 0;
501 mike     1.2       }

No CVS admin address has been configured
Powered by
ViewCVS 0.9.2