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

Diff for /pegasus/src/Pegasus/Common/HTTPAcceptor.cpp between version 1.61 and 1.91

version 1.61, 2005/02/15 01:28:51 version 1.91, 2006/10/25 19:48:12
Line 1 
Line 1 
 //%2005////////////////////////////////////////////////////////////////////////  //%2006////////////////////////////////////////////////////////////////////////
 // //
 // Copyright (c) 2000, 2001, 2002 BMC Software; Hewlett-Packard Development // Copyright (c) 2000, 2001, 2002 BMC Software; Hewlett-Packard Development
 // Company, L.P.; IBM Corp.; The Open Group; Tivoli Systems. // Company, L.P.; IBM Corp.; The Open Group; Tivoli Systems.
Line 8 
Line 8 
 // IBM Corp.; EMC Corporation; VERITAS Software Corporation; The Open Group. // IBM Corp.; EMC Corporation; VERITAS Software Corporation; The Open Group.
 // Copyright (c) 2005 Hewlett-Packard Development Company, L.P.; IBM Corp.; // Copyright (c) 2005 Hewlett-Packard Development Company, L.P.; IBM Corp.;
 // EMC Corporation; VERITAS Software Corporation; The Open Group. // EMC Corporation; VERITAS Software Corporation; The Open Group.
   // Copyright (c) 2006 Hewlett-Packard Development Company, L.P.; IBM Corp.;
   // EMC Corporation; Symantec Corporation; The Open Group.
 // //
 // Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to // of this software and associated documentation files (the "Software"), to
Line 27 
Line 29 
 // //
 //============================================================================== //==============================================================================
 // //
 // Author: Mike Brasher (mbrasher@bmc.com)  
 //  
 // Modified By:  
 //         Jenny Yu, Hewlett-Packard Company (jenny_yu@hp.com)  
 //         Nag Boranna, Hewlett-Packard Company (nagaraja_boranna@hp.com)  
 //         Dave Rosckes (rosckes@us.ibm.com)  
 //         Denise Eckstein (denise.eckstein@hp.com)  
 //         Alagaraja Ramasubramanian (alags_raj@in.ibm.com) for Bug#1090  
 //         Amit Arora, IBM (amita@in.ibm.com) for Bug#2541  
 //         Roger Kumpf, Hewlett-Packard Company (roger_kumpf@hp.com)  
 //         Sean Keenan, Hewlett-Packard Company (sean.keenan@hp.com)  
 //  
 //%///////////////////////////////////////////////////////////////////////////// //%/////////////////////////////////////////////////////////////////////////////
  
 #include "Config.h" #include "Config.h"
 #include "Constants.h" #include "Constants.h"
 #include <iostream> #include <iostream>
 #include "Socket.h"  
   
 #ifdef PEGASUS_PLATFORM_WIN32_IX86_MSVC  
 #include <windows.h>  
 #else  
 # include <cctype>  
 # include <cstdlib>  
 # include <errno.h>  
 # include <fcntl.h>  
 # include <netdb.h>  
 # include <netinet/in.h>  
 # include <arpa/inet.h>  
 # include <sys/socket.h>  
 # ifdef PEGASUS_LOCAL_DOMAIN_SOCKET  
 # include <unistd.h>  
 #  include <sys/un.h>  
 # endif  
 #endif  
  
   #include "Network.h"
 #include "Socket.h" #include "Socket.h"
 #include "TLS.h" #include "TLS.h"
 #include "HTTPAcceptor.h" #include "HTTPAcceptor.h"
Line 71 
Line 44 
 #include <Pegasus/Common/MessageLoader.h> //l10n #include <Pegasus/Common/MessageLoader.h> //l10n
  
 #ifdef PEGASUS_PLATFORM_OS400_ISERIES_IBM #ifdef PEGASUS_PLATFORM_OS400_ISERIES_IBM
 #include "OS400ConvertChar.h"  #include "EBCDIC_OS400.h"
 #endif #endif
  
   
 PEGASUS_USING_STD; PEGASUS_USING_STD;
  
 PEGASUS_NAMESPACE_BEGIN PEGASUS_NAMESPACE_BEGIN
  
 static int MAX_CONNECTION_QUEUE_LENGTH = -1;  
  
   static int MAX_CONNECTION_QUEUE_LENGTH = -1;
  
 //////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
 // //
Line 94 
Line 68 
     {     {
         if (local)         if (local)
         {         {
 #ifdef PEGASUS_LOCAL_DOMAIN_SOCKET  #ifndef PEGASUS_DISABLE_LOCAL_DOMAIN_SOCKET
             address = reinterpret_cast<struct sockaddr*>(new struct sockaddr_un);             address = reinterpret_cast<struct sockaddr*>(new struct sockaddr_un);
             address_size = sizeof(struct sockaddr_un);             address_size = sizeof(struct sockaddr_un);
 #else #else
Line 107 
Line 81 
             address_size = sizeof(struct sockaddr_in);             address_size = sizeof(struct sockaddr_in);
         }         }
     }     }
       ~HTTPAcceptorRep()
       {
           delete address;
       }
     struct sockaddr* address;     struct sockaddr* address;
  
 #if defined(PEGASUS_PLATFORM_ZOS_ZSERIES_IBM) || defined(PEGASUS_OS_VMS)      SocketLength address_size;
    size_t address_size;  
 #elif defined(PEGASUS_PLATFORM_AIX_RS_IBMCXX) || defined(PEGASUS_PLATFORM_LINUX_GENERIC_GNU) || (defined(PEGASUS_PLATFORM_SOLARIS_SPARC_CC) && !defined(SUNOS_5_6))  
    socklen_t address_size;  
 #else  
    int address_size;  
 #endif  
       Mutex _connection_mut;       Mutex _connection_mut;
  
       Sint32 socket;      SocketHandle socket;
       Array<HTTPConnection*> connections;       Array<HTTPConnection*> connections;
 }; };
  
   
 //////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
 // //
 // HTTPAcceptor // HTTPAcceptor
Line 134 
Line 106 
                            Boolean localConnection,                            Boolean localConnection,
                            Uint32 portNumber,                            Uint32 portNumber,
                            SSLContext * sslcontext,                            SSLContext * sslcontext,
                            Boolean exportConnection,  
                            ReadWriteSem* sslContextObjectLock)                            ReadWriteSem* sslContextObjectLock)
    : Base(PEGASUS_QUEUENAME_HTTPACCEPTOR),  // ATTN: Need unique names?    : Base(PEGASUS_QUEUENAME_HTTPACCEPTOR),  // ATTN: Need unique names?
      _monitor(monitor),      _monitor(monitor),
Line 144 
Line 115 
      _localConnection(localConnection),      _localConnection(localConnection),
      _portNumber(portNumber),      _portNumber(portNumber),
      _sslcontext(sslcontext),      _sslcontext(sslcontext),
      _exportConnection(exportConnection),  
      _sslContextObjectLock(sslContextObjectLock)      _sslContextObjectLock(sslContextObjectLock)
 { {
    Socket::initializeInterface();    Socket::initializeInterface();
Line 187 
Line 157 
  
 HTTPAcceptor::~HTTPAcceptor() HTTPAcceptor::~HTTPAcceptor()
 { {
      destroyConnections();
    unbind();    unbind();
    // ATTN: Is this correct in a multi-HTTPAcceptor server?    // ATTN: Is this correct in a multi-HTTPAcceptor server?
    Socket::uninitializeInterface();    Socket::uninitializeInterface();
Line 197 
Line 168 
    if (! message)    if (! message)
       return;       return;
  
      PEGASUS_ASSERT(_rep != 0);
    switch (message->getType())    switch (message->getType())
    {    {
       case SOCKET_MESSAGE:       case SOCKET_MESSAGE:
Line 206 
Line 178 
          // If this is a connection request:          // If this is a connection request:
  
          if (socketMessage->socket == _rep->socket &&          if (socketMessage->socket == _rep->socket &&
              socketMessage->events | SocketMessage::READ)           socketMessage->events & SocketMessage::READ)
          {          {
             _acceptConnection();             _acceptConnection();
          }          }
Line 230 
Line 202 
          for (Uint32 i = 0, n = _rep->connections.size(); i < n; i++)          for (Uint32 i = 0, n = _rep->connections.size(); i < n; i++)
          {          {
             HTTPConnection* connection = _rep->connections[i];             HTTPConnection* connection = _rep->connections[i];
             Sint32 socket = connection->getSocket();          SocketHandle socket = connection->getSocket();
  
             if (socket == closeConnectionMessage->socket)             if (socket == closeConnectionMessage->socket)
             {             {
Line 294 
Line 266 
  
 /** /**
    _bind - creates a new server socket and bind socket to the port address.    _bind - creates a new server socket and bind socket to the port address.
    If PEGASUS_LOCAL_DOMAIN_SOCKET is defined, the port number is ignored and     If PEGASUS_DISABLE_LOCAL_DOMAIN_SOCKET is not defined, the port number is ignored and
    a domain socket is bound.    a domain socket is bound.
 */ */
 void HTTPAcceptor::_bind() void HTTPAcceptor::_bind()
 { {
  
      PEGASUS_ASSERT(_rep != 0);
    // Create address:    // Create address:
  
    memset(_rep->address, 0, sizeof(*_rep->address));    memset(_rep->address, 0, sizeof(*_rep->address));
  
    if (_localConnection)    if (_localConnection)
    {    {
 #ifdef PEGASUS_LOCAL_DOMAIN_SOCKET  #ifndef PEGASUS_DISABLE_LOCAL_DOMAIN_SOCKET
        reinterpret_cast<struct sockaddr_un*>(_rep->address)->sun_family =        reinterpret_cast<struct sockaddr_un*>(_rep->address)->sun_family =
            AF_UNIX;            AF_UNIX;
        strcpy(reinterpret_cast<struct sockaddr_un*>(_rep->address)->sun_path,        strcpy(reinterpret_cast<struct sockaddr_un*>(_rep->address)->sun_path,
Line 333 
Line 306 
  
    if (_localConnection)    if (_localConnection)
    {    {
        _rep->socket = socket(AF_UNIX, SOCK_STREAM, 0);         _rep->socket = Socket::createSocket(AF_UNIX, SOCK_STREAM, 0);
    }    }
    else    else
    {    {
        _rep->socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);         _rep->socket = Socket::createSocket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
   
    }    }
  
    if (_rep->socket < 0)    if (_rep->socket < 0)
Line 356 
Line 330 
  
 // set the close-on-exec bit for this file handle. // set the close-on-exec bit for this file handle.
 // any unix that forks needs this bit set. // any unix that forks needs this bit set.
 #if !defined PEGASUS_PLATFORM_WIN32_IX86_MSVC && !defined(PEGASUS_OS_VMS)  #if !defined PEGASUS_OS_TYPE_WINDOWS && !defined(PEGASUS_OS_VMS)
    int sock_flags;    int sock_flags;
  if( (sock_flags = fcntl(_rep->socket, F_GETFD, 0)) < 0)  if( (sock_flags = fcntl(_rep->socket, F_GETFD, 0)) < 0)
    {    {
Line 397 
Line 371 
       throw BindFailedException(parms);       throw BindFailedException(parms);
    }    }
  
    // Bind socket to port:  
  
      //
      // Bind socket to port:
      //
    if (::bind(_rep->socket, _rep->address, _rep->address_size) < 0)    if (::bind(_rep->socket, _rep->address, _rep->address_size) < 0)
    {    {
       Socket::close(_rep->socket);       Socket::close(_rep->socket);
Line 413 
Line 389 
       throw BindFailedException(parms);       throw BindFailedException(parms);
    }    }
  
   
      //
      // Get the actual port value used if the caller specified a port value of 0.
      //
      if ( _portNumber == 0 )
      {
         sockaddr_in buf;
         SocketLength bufSize = sizeof(buf);
         if ( getsockname(_rep->socket, reinterpret_cast<sockaddr *>(&buf), &bufSize) == 0 )
         {
             _portNumber = ntohs(buf.sin_port);
         }
      }
   
   
    //    //
    //  Change permissions on Linux local domain socket to allow writes by others.    //  Change permissions on Linux local domain socket to allow writes by others.
    //    //
 #if defined(PEGASUS_LOCAL_DOMAIN_SOCKET) && defined(PEGASUS_PLATFORM_LINUX_GENERIC_GNU)  #if !defined(PEGASUS_DISABLE_LOCAL_DOMAIN_SOCKET) && \
        ( defined(PEGASUS_PLATFORM_LINUX_GENERIC_GNU) || \
          defined(PEGASUS_PLATFORM_ZOS_ZSERIES_IBM) )
    if (_localConnection)    if (_localConnection)
    {    {
      if (::chmod( PEGASUS_LOCAL_DOMAIN_SOCKET_PATH,      if (::chmod( PEGASUS_LOCAL_DOMAIN_SOCKET_PATH,
Line 492 
Line 485 
  
       // close the socket       // close the socket
       Socket::close(_rep->socket);       Socket::close(_rep->socket);
         // Unlink Local Domain Socket Bug# 3312
         if (_localConnection)
         {
   #ifndef PEGASUS_DISABLE_LOCAL_DOMAIN_SOCKET
             PEG_TRACE_STRING(TRC_HTTP, Tracer::LEVEL2,
                           "HTTPAcceptor::closeConnectionSocket Unlinking local connection." );
            ::unlink(
                reinterpret_cast<struct sockaddr_un*>(_rep->address)->sun_path);
   #else
            PEGASUS_ASSERT(false);
   #endif
         }
   
    }    }
    else    else
    {    {
Line 516 
Line 522 
    }    }
 } }
  
   
   /**
      reconnectConnectionSocket - creates a new server socket.
   */
   void HTTPAcceptor::reconnectConnectionSocket()
   {
      if (_rep)
      {
         // unregister the socket
         _monitor->unsolicitSocketMessages(_rep->socket);
         // close the socket
         Socket::close(_rep->socket);
         // Unlink Local Domain Socket Bug# 3312
         if (_localConnection)
         {
   #ifndef PEGASUS_DISABLE_LOCAL_DOMAIN_SOCKET
             PEG_TRACE_STRING(TRC_HTTP, Tracer::LEVEL2,
                           "HTTPAcceptor::reconnectConnectionSocket Unlinking local connection." );
            ::unlink(
                reinterpret_cast<struct sockaddr_un*>(_rep->address)->sun_path);
   #else
            PEGASUS_ASSERT(false);
   #endif
         }
         // open the socket
         _bind();
      }
      else
      {
         PEG_TRACE_STRING(TRC_DISCARDED_DATA, Tracer::LEVEL2,
                           "HTTPAcceptor::reconnectConnectionSocket failure _rep is null." );
      }
   }
   
 /** /**
    getOutstandingRequestCount - returns the number of outstanding requests.    getOutstandingRequestCount - returns the number of outstanding requests.
 */ */
 Uint32 HTTPAcceptor::getOutstandingRequestCount()  Uint32 HTTPAcceptor::getOutstandingRequestCount() const
 { {
    Uint32 count = 0;    Uint32 count = 0;
      if (_rep)
      {
    AutoMutex autoMut(_rep->_connection_mut);    AutoMutex autoMut(_rep->_connection_mut);
    if (_rep->connections.size() > 0)    if (_rep->connections.size() > 0)
    {    {
       HTTPConnection* connection = _rep->connections[0];       HTTPConnection* connection = _rep->connections[0];
       count = connection->getRequestCount();       count = connection->getRequestCount();
    }    }
      }
    return count;    return count;
 } }
  
   
   /**
       getPortNumber - returns the port number used for the connection
   */
   Uint32 HTTPAcceptor::getPortNumber() const
   {
       return _portNumber;
   }
   
   void HTTPAcceptor::setSocketWriteTimeout(Uint32 socketWriteTimeout)
   {
       _socketWriteTimeout = socketWriteTimeout;
   }
   
 void HTTPAcceptor::unbind() void HTTPAcceptor::unbind()
 { {
    if (_rep)    if (_rep)
    {    {
         _portNumber = 0;
       Socket::close(_rep->socket);       Socket::close(_rep->socket);
  
       if (_localConnection)       if (_localConnection)
       {       {
 #ifdef PEGASUS_LOCAL_DOMAIN_SOCKET  #ifndef PEGASUS_DISABLE_LOCAL_DOMAIN_SOCKET
          ::unlink(          ::unlink(
              reinterpret_cast<struct sockaddr_un*>(_rep->address)->sun_path);              reinterpret_cast<struct sockaddr_un*>(_rep->address)->sun_path);
 #else #else
Line 561 
Line 617 
  
 void HTTPAcceptor::destroyConnections() void HTTPAcceptor::destroyConnections()
 { {
      if (_rep)
      {
    // For each connection created by this object:    // For each connection created by this object:
  
    AutoMutex autoMut(_rep->_connection_mut);    AutoMutex autoMut(_rep->_connection_mut);
    for (Uint32 i = 0, n = _rep->connections.size(); i < n; i++)    for (Uint32 i = 0, n = _rep->connections.size(); i < n; i++)
    {    {
       HTTPConnection* connection = _rep->connections[i];       HTTPConnection* connection = _rep->connections[i];
       Sint32 socket = connection->getSocket();          SocketHandle socket = connection->getSocket();
  
       // Unsolicit SocketMessages:       // Unsolicit SocketMessages:
  
Line 577 
Line 633 
  
       // Destroy the connection (causing it to close):       // Destroy the connection (causing it to close):
  
       while (connection->refcount.value()) { }          while (connection->refcount.get()) { }
       delete connection;       delete connection;
    }    }
  
    _rep->connections.clear();    _rep->connections.clear();
      }
 } }
  
 void HTTPAcceptor::_acceptConnection() void HTTPAcceptor::_acceptConnection()
Line 591 
Line 647 
  
    PEGASUS_ASSERT(_rep != 0);    PEGASUS_ASSERT(_rep != 0);
  
    if (!_rep)  
       return;  
   
    // Accept the connection (populate the address):    // Accept the connection (populate the address):
  
    struct sockaddr* accept_address;    struct sockaddr* accept_address;
 #if defined(PEGASUS_PLATFORM_ZOS_ZSERIES_IBM) || defined(PEGASUS_OS_VMS)     SocketLength address_size;
    size_t address_size;  
 #elif defined(PEGASUS_PLATFORM_AIX_RS_IBMCXX) || defined(PEGASUS_PLATFORM_LINUX_GENERIC_GNU) || (defined(PEGASUS_OS_SOLARIS) && !defined(SUNOS_5_6))  
    socklen_t address_size;  
 #else  
    int address_size;  
 #endif  
  
    if (_localConnection)    if (_localConnection)
    {    {
 #ifdef PEGASUS_LOCAL_DOMAIN_SOCKET  #ifndef PEGASUS_DISABLE_LOCAL_DOMAIN_SOCKET
        accept_address = reinterpret_cast<struct sockaddr*>(new struct sockaddr_un);        accept_address = reinterpret_cast<struct sockaddr*>(new struct sockaddr_un);
        address_size = sizeof(struct sockaddr_un);        address_size = sizeof(struct sockaddr_un);
 #else #else
Line 620 
Line 667 
        address_size = sizeof(struct sockaddr_in);        address_size = sizeof(struct sockaddr_in);
    }    }
  
    Sint32 socket = accept(_rep->socket, accept_address, &address_size);     SocketHandle socket = accept(_rep->socket, accept_address, &address_size);
  
      if (socket == PEGASUS_SOCKET_ERROR)
      {
          // the remote connection is invalid, destroy client address.
    delete accept_address;    delete accept_address;
  
    if (socket < 0)         // TCPIP is down reconnect this acceptor
          if(getSocketError() == PEGASUS_NETWORK_TCPIP_STOPPED)
    {    {
  
              PEG_TRACE_STRING(TRC_DISCARDED_DATA, Tracer::LEVEL2,
                       "Socket has an IO error. TCP/IP down. Try to reconnect." );
   
              reconnectConnectionSocket();
   
              return;
          }
        Logger::put(Logger::STANDARD_LOG, System::CIMSERVER, Logger::TRACE,        Logger::put(Logger::STANDARD_LOG, System::CIMSERVER, Logger::TRACE,
                    "HTTPAcceptor - accept() failure.  errno: $0"                    "HTTPAcceptor - accept() failure.  errno: $0"
                    ,errno);                    ,errno);
Line 636 
Line 694 
       return;       return;
    }    }
  
      String ipAddress;
   
      if (_localConnection)
      {
          ipAddress = "localhost";
      }
      else
      {
          unsigned char* sa = reinterpret_cast<unsigned char*>(
              &reinterpret_cast<struct sockaddr_in*>(
                  accept_address)->sin_addr.s_addr);
          char ipBuffer[32];
          sprintf(ipBuffer, "%u.%u.%u.%u", sa[0], sa[1], sa[2], sa[3]);
          ipAddress = ipBuffer;
      }
   
      delete accept_address;
   
 // set the close on exec flag // set the close on exec flag
 #if !defined PEGASUS_PLATFORM_WIN32_IX86_MSVC && !defined(PEGASUS_OS_VMS)  #if !defined(PEGASUS_OS_TYPE_WINDOWS) && !defined(PEGASUS_OS_VMS)
    int sock_flags;    int sock_flags;
  if( (sock_flags = fcntl(socket, F_GETFD, 0)) < 0)  if( (sock_flags = fcntl(socket, F_GETFD, 0)) < 0)
    {    {
Line 656 
Line 732 
 #endif #endif
  
  
    Logger::put(Logger::STANDARD_LOG, System::CIMSERVER, Logger::TRACE,     PEG_LOGGER_TRACE((Logger::STANDARD_LOG, System::CIMSERVER, 0,
                "HTTPAcceptor - accept() success.  Socket: $1"         "HTTPAcceptor - accept() success.  Socket: $1" ,socket));
                ,socket);  
  
    // Create a new conection and add it to the connection list:     AutoPtr<MP_Socket> mp_socket(new MP_Socket(
          socket, _sslcontext, _sslContextObjectLock));
  
    AutoPtr<MP_Socket> mp_socket(new MP_Socket(socket, _sslcontext, _exportConnection));     mp_socket->setSocketWriteTimeout(_socketWriteTimeout);
  
    Sint32 retVal;     // Perform the SSL handshake, if applicable.  Make the socket non-blocking
      // for this operation so we can send it back to the Monitor's select() loop
      // if it takes a while.
  
    if (_sslcontext)     mp_socket->disableBlocking();
    {     Sint32 socketAcceptStatus = mp_socket->accept();
        //     mp_socket->enableBlocking();
        // For SSL connections, obtain read lock to SSLContext object before  
        // calling the accept() method of MP_Socket.  
        //  
        ReadLock rlock(*_sslContextObjectLock);  
        retVal = mp_socket->accept();  
    }  
    else  
    {  
        retVal = mp_socket->accept();  
    }  
  
    if (retVal < 0)     if (socketAcceptStatus < 0)
    {    {
        PEG_TRACE_STRING(TRC_DISCARDED_DATA, Tracer::LEVEL2,        PEG_TRACE_STRING(TRC_DISCARDED_DATA, Tracer::LEVEL2,
                         "HTTPAcceptor: SSL_accept() failed");                         "HTTPAcceptor: SSL_accept() failed");
Line 688 
Line 756 
        return;        return;
    }    }
  
      // Create a new connection and add it to the connection list:
   
    HTTPConnection* connection = new HTTPConnection(_monitor, mp_socket,    HTTPConnection* connection = new HTTPConnection(_monitor, mp_socket,
        this, static_cast<MessageQueue *>(_outputMessageQueue), _exportConnection);         ipAddress, this, static_cast<MessageQueue *>(_outputMessageQueue));
   
      if (socketAcceptStatus == 0)
      {
          PEG_TRACE_STRING(TRC_HTTP, Tracer::LEVEL2,
              "HTTPAcceptor: SSL_accept() pending");
          connection->_acceptPending = true;
      }
  
    // Solicit events on this new connection's socket:    // Solicit events on this new connection's socket:
    int index;    int index;
Line 712 
Line 789 
    connection->_entry_index = index;    connection->_entry_index = index;
    AutoMutex autoMut(_rep->_connection_mut);    AutoMutex autoMut(_rep->_connection_mut);
    _rep->connections.append(connection);    _rep->connections.append(connection);
   
 } }
  
 PEGASUS_NAMESPACE_END PEGASUS_NAMESPACE_END


Legend:
Removed from v.1.61  
changed lines
  Added in v.1.91

No CVS admin address has been configured
Powered by
ViewCVS 0.9.2