(file) Return to PAMBasicAuthenticatorUnix.cpp CVS log (file) (dir) Up to [Pegasus] / pegasus / src / Pegasus / Security / Authentication

Diff for /pegasus/src/Pegasus/Security/Authentication/PAMBasicAuthenticatorUnix.cpp between version 1.4 and 1.14.4.1

version 1.4, 2002/05/21 19:08:43 version 1.14.4.1, 2004/03/25 21:29:58
Line 1 
Line 1 
 //%/////////////////////////////////////////////////////////////////////////////  //%2003////////////////////////////////////////////////////////////////////////
 //  
 // Copyright (c) 2000, 2001 BMC Software, Hewlett-Packard Company, IBM,  
 // The Open Group, Tivoli Systems  
 // //
 // Permission is hereby granted, free of charge, to any person obtaining a  // Copyright (c) 2000, 2001, 2002  BMC Software, Hewlett-Packard Development
 // copy of this software and associated documentation files (the "Software"),  // Company, L. P., IBM Corp., The Open Group, Tivoli Systems.
 // to deal in the Software without restriction, including without limitation  // Copyright (c) 2003 BMC Software; Hewlett-Packard Development Company, L. P.;
 // the rights to use, copy, modify, merge, publish, distribute, sublicense,  // IBM Corp.; EMC Corporation, The Open Group.
 // and/or sell copies of the Software, and to permit persons to whom the  //
 // Software is furnished to do so, subject to the following conditions:  // Permission is hereby granted, free of charge, to any person obtaining a copy
 //  // of this software and associated documentation files (the "Software"), to
 // The above copyright notice and this permission notice shall be included in  // deal in the Software without restriction, including without limitation the
 // all copies of substantial portions of this software.  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 //  // sell copies of the Software, and to permit persons to whom the Software is
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  // furnished to do so, subject to the following conditions:
 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  //
 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  // THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN
 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  // ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED
 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER  // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 // DEALINGS IN THE SOFTWARE.  // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 // //
 //============================================================================== //==============================================================================
 // //
 // Author: Nag Boranna, Hewlett-Packard Company(nagaraja_boranna@hp.com) // Author: Nag Boranna, Hewlett-Packard Company(nagaraja_boranna@hp.com)
 // //
 // Modified By:  // Modified By: Yi Zhou, Hewlett-Packard Company(yi_zhou@hp.com)
   //            : Sushma Fernandes, Hewlett-Packard Company
   //                (sushma_fernandes@hp.com)
 // //
 //%///////////////////////////////////////////////////////////////////////////// //%/////////////////////////////////////////////////////////////////////////////
  
Line 33 
Line 35 
 #include <Pegasus/Common/Tracer.h> #include <Pegasus/Common/Tracer.h>
 #include <Pegasus/Common/Destroyer.h> #include <Pegasus/Common/Destroyer.h>
 #include <Pegasus/Config/ConfigManager.h> #include <Pegasus/Config/ConfigManager.h>
   #include <Pegasus/Common/FileSystem.h>
  
 #include "PAMBasicAuthenticator.h"  #if defined (PEGASUS_OS_HPUX)
   #include <prot.h>
   #endif
   
   #if defined (PEGASUS_USE_PAM_STANDALONE_PROC)
   #include <Pegasus/Common/Signal.h>
   #include <Pegasus/Common/Logger.h>
   #include <Pegasus/Common/IPC.h>
   #include <pwd.h>
   #include <sys/stat.h>
   #include <unistd.h>
   #include <sys/types.h>
   #include <sys/resource.h>
   #endif
  
   #include "PAMBasicAuthenticator.h"
  
 PEGASUS_USING_STD; PEGASUS_USING_STD;
  
Line 43 
Line 60 
  
 #include <security/pam_appl.h> #include <security/pam_appl.h>
  
   #define BUFFERLEN 1024
  
 /** /**
     Constant representing the Basic authentication challenge header.     Constant representing the Basic authentication challenge header.
 */ */
 static const String BASIC_CHALLENGE_HEADER = "WWW-Authenticate: Basic \""; static const String BASIC_CHALLENGE_HEADER = "WWW-Authenticate: Basic \"";
  
   Mutex PAMBasicAuthenticator::_authSerializeMutex;
  
 /** Service name for pam_start */ /** Service name for pam_start */
 const char *service = "wbem"; const char *service = "wbem";
  
 char* userPassword = 0;  typedef struct
   {
       CString userPassword;
   } APP_DATA;
  
 /* constructor. */ /* constructor. */
 PAMBasicAuthenticator::PAMBasicAuthenticator() PAMBasicAuthenticator::PAMBasicAuthenticator()
Line 79 
Line 101 
     _realm.append(":");     _realm.append(":");
     _realm.append(port);     _realm.append(port);
  
       //
       // Check for platforms that allow PAM
       //
   #if defined(PEGASUS_OS_HPUX) || defined(PEGASUS_PLATFORM_LINUX_GENERIC_GNU)
       //
       // get the configured usePAMAuthentication flag
       //
       if (String::equal(
           configManager->getCurrentValue("usePAMAuthentication"), "true"))
       {
           _usePAM = true;
       }
       else
       {
           _usePAM = false;
       }
   #if defined(PEGASUS_USE_PAM_STANDALONE_PROC)
       //
       // Set up the separate process to do PAM Authentication
       //
       if (_usePAM)
       {
           _pamBasicAuthenticatorStandAlone =
               new PAMBasicAuthenticatorStandAlone();
       }
   #endif
   
   #endif
   
     PEG_METHOD_EXIT();     PEG_METHOD_EXIT();
 } }
  
Line 98 
Line 149 
     PEG_METHOD_ENTER(TRC_AUTHENTICATION,     PEG_METHOD_ENTER(TRC_AUTHENTICATION,
         "PAMBasicAuthenticator::authenticate()");         "PAMBasicAuthenticator::authenticate()");
  
       Boolean authenticated;
   
   #if !defined(PEGASUS_USE_PAM_STANDALONE_PROC)
       authenticated = _authenticateByPAM(userName, password);
   #else
       //
       // Mutex to Serialize Authentication calls.
       //
       Tracer::trace(TRC_AUTHENTICATION, Tracer::LEVEL4,
           "Authentication Mutex lock.");
       AutoMutex lock(_authSerializeMutex);
   
       if (_usePAM)
       {
           authenticated =
               _pamBasicAuthenticatorStandAlone->authenticate(userName,
                                                                 password);
       }
       else
       {
           authenticated = _authenticateByPwnam(userName.getCString(), password);
       }
   #endif
   
       PEG_METHOD_EXIT();
       return (authenticated);
   }
   
   Boolean PAMBasicAuthenticator::_authenticateByPAM(
       const String& userName,
       const String& password)
   {
       PEG_METHOD_ENTER(TRC_AUTHENTICATION,
           "PAMBasicAuthenticator::_authenticateByPAM()");
   
     Boolean authenticated = false;     Boolean authenticated = false;
     struct pam_conv pconv;     struct pam_conv pconv;
     pam_handle_t *phandle;     pam_handle_t *phandle;
     char *name;     char *name;
       APP_DATA mydata;
  
     pconv.conv = PAMBasicAuthenticator::PAMCallback;      //
     pconv.appdata_ptr = NULL;      // Store the password for PAM authentication
       //
       mydata.userPassword = password.getCString();
  
     ArrayDestroyer<char> p(password.allocateCString());      pconv.conv = PAMBasicAuthenticator::PAMCallback;
     userPassword = p.getPointer();      pconv.appdata_ptr = &mydata;
  
     ArrayDestroyer<char> user(userName.allocateCString());  //    WARNING: Should only be uncommented for debugging in a secure environment.
   //    Tracer::trace(TRC_AUTHENTICATION, Tracer::LEVEL4,
   //       "PAMBasicAuthenticator::_authenticateByPAM() - userName = %s; userPassword = %s",
   //       (const char *)userName.getCString(), (const char *)password.getCString());
  
     //     //
     //Call pam_start since you need to before making any other PAM calls     //Call pam_start since you need to before making any other PAM calls
     //     //
     if ( ( pam_start(service,     if ( ( pam_start(service,
         (const char *)user.getPointer(), &pconv, &phandle) ) != PAM_SUCCESS )          (const char *)userName.getCString(), &pconv, &phandle) ) != PAM_SUCCESS )
     {     {
         userPassword = 0;  
         PEG_METHOD_EXIT();         PEG_METHOD_EXIT();
         return (authenticated);         return (authenticated);
     }     }
Line 127 
Line 218 
     //     //
     if ( ( pam_authenticate(phandle, 0) ) == PAM_SUCCESS )     if ( ( pam_authenticate(phandle, 0) ) == PAM_SUCCESS )
     {     {
          Tracer::trace(TRC_AUTHENTICATION, Tracer::LEVEL4,
            "pam_authenticate successful.");
         //         //
         //Call pam_acct_mgmt, to check if the user account is valid. This includes         //Call pam_acct_mgmt, to check if the user account is valid. This includes
         //checking for password and account expiration, as well as verifying access         //checking for password and account expiration, as well as verifying access
Line 134 
Line 227 
         //         //
         if ( ( pam_acct_mgmt(phandle, 0) ) == PAM_SUCCESS )         if ( ( pam_acct_mgmt(phandle, 0) ) == PAM_SUCCESS )
         {         {
              Tracer::trace(TRC_AUTHENTICATION, Tracer::LEVEL4,
                 "pam_acct_mgmt successful.");
             authenticated = true;             authenticated = true;
         }         }
     }     }
Line 143 
Line 238 
     //     //
     pam_end(phandle, 0);     pam_end(phandle, 0);
  
     userPassword = 0;  
     PEG_METHOD_EXIT();     PEG_METHOD_EXIT();
  
     return (authenticated);     return (authenticated);
 } }
  
   #if defined(PEGASUS_USE_PAM_STANDALONE_PROC)
   Boolean PAMBasicAuthenticator::_authenticateByPwnam(
       const char * userName,
       const String& password)
   {
       PEG_METHOD_ENTER(TRC_AUTHENTICATION,
           "PAMBasicAuthenticator::_authenticateByPwnam()");
   
       Boolean authenticated = false;
   
       String currPassword         = String::EMPTY;
       String encryptedPassword    = String::EMPTY;
       String saltStr              = String::EMPTY;
   
       //
       // check if the system has been converted to a trusted system.
       //
   
   #if defined(PEGASUS_OS_HPUX)
       if (iscomsec())
       {
           // system is a trusted system
           // use interface getprpwnam to get pr_passwd structure
   
           struct pr_passwd * pwd;
   
           char* _userName = strcpy(new char[strlen(userName) + 1], userName);
   
           // getprpwnam returns a pointer to a pr_passwd structure  upon success
           if ( (pwd = getprpwnam(_userName)) != NULL)
           {
              Tracer::trace(TRC_AUTHENTICATION, Tracer::LEVEL4,
                 "getprpwnam successful.");
              // get user's password from pr_passwd structure
               currPassword = pwd->ufld.fd_encrypt;
           }
   
           delete [] _userName;
       }
       else
       {
   #endif
           //
           // system is not a trusted system
           // use reentrant interface getpwnam_r to get password structure
           //
           struct passwd pwd;
           struct passwd *result;
           char pwdBuffer[BUFFERLEN];
   
           // getpwnam_r returns zero upon success
           if (getpwnam_r(userName, &pwd, pwdBuffer, BUFFERLEN, &result) == 0)
           {
              Tracer::trace(TRC_AUTHENTICATION, Tracer::LEVEL4,
                 "getpwnam_r successful.");
              // get user's password from password file
               currPassword = pwd.pw_passwd;
           }
   #if defined(PEGASUS_OS_HPUX)
       }
   #endif
   
       //
       // Check if the specified password mathches user's password
       //
       saltStr = currPassword.subString(0,2);
   
       encryptedPassword = System::encryptPassword(password.getCString(),
                           saltStr.getCString());
   
       if (String::equal(currPassword, encryptedPassword))
       {
           authenticated = true;
           Tracer::trace(TRC_AUTHENTICATION, Tracer::LEVEL4,
                     "Password match successful.");
       }
   
       PEG_METHOD_EXIT();
   
       return (authenticated);
   }
   #endif
   
 // //
 // Create authentication response header // Create authentication response header
 // //
Line 169 
Line 346 
     return (responseHeader);     return (responseHeader);
 } }
  
   #if defined PEGASUS_OS_LINUX
   Sint32 PAMBasicAuthenticator::PAMCallback(Sint32 num_msg, const struct pam_message **msg,
           struct pam_response **resp, void *appdata_ptr)
   #else
 Sint32 PAMBasicAuthenticator::PAMCallback(Sint32 num_msg, struct pam_message **msg, Sint32 PAMBasicAuthenticator::PAMCallback(Sint32 num_msg, struct pam_message **msg,
         struct pam_response **resp, void *appdata_ptr)         struct pam_response **resp, void *appdata_ptr)
   #endif
 { {
     PEG_METHOD_ENTER(TRC_AUTHENTICATION,     PEG_METHOD_ENTER(TRC_AUTHENTICATION,
         "PAMBasicAuthenticator::PAMCallback()");         "PAMBasicAuthenticator::PAMCallback()");
   
       //
       // Copy the application specific data from the PAM structure.
       //
       APP_DATA *mydata;
       mydata = (APP_DATA *) appdata_ptr;
   
     //     //
     // Allocate the response buffers     // Allocate the response buffers
     //     //
     if ( num_msg > 0 )     if ( num_msg > 0 )
     {     {
         *resp = (struct pam_response *)malloc(sizeof(struct pam_response)*num_msg);          //
           // Since resp->resp needs to be initialized in all possible scenarios,
           // use calloc for memory allocation.
           //
           *resp = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response));
  
         if ( *resp == NULL )         if ( *resp == NULL )
         {         {
Line 193 
Line 386 
         return PAM_CONV_ERR;         return PAM_CONV_ERR;
     }     }
  
     for ( Uint32 i = 0; i < num_msg; i++ )      for ( Sint32 i = 0; i < num_msg; i++ )
     {     {
         switch ( msg[i]->msg_style )         switch ( msg[i]->msg_style )
         {         {
Line 202 
Line 395 
                 // copy the user password                 // copy the user password
                 //                 //
                 resp[i]->resp = (char *)malloc(PAM_MAX_MSG_SIZE);                 resp[i]->resp = (char *)malloc(PAM_MAX_MSG_SIZE);
                 strcpy(resp[i]->resp, userPassword);                  strcpy(resp[i]->resp, mydata->userPassword);
                 resp[i]->resp_retcode = 0;                 resp[i]->resp_retcode = 0;
                 break;                 break;
  
Line 217 
Line 410 
     return PAM_SUCCESS;     return PAM_SUCCESS;
 } }
  
   /** Routines to access PAM Authentication via a standalone process **/
   
   #if defined(PEGASUS_USE_PAM_STANDALONE_PROC)
   
   int     fd_1[2], fd_2[2];
   Boolean continue_PAMauthentication;
   Boolean printed_err_since_success=false;
   
   /* constructor. */
   PAMBasicAuthenticatorStandAlone::PAMBasicAuthenticatorStandAlone()
   {
       PEG_METHOD_ENTER(TRC_AUTHENTICATION,
           "PAMBasicAuthenticatorStandAlone::PAMBasicAuthenticatorStandAlone()");
   
       _createPAMStandalone();
   
       PEG_METHOD_EXIT();
   }
   
   /* destructor. */
   PAMBasicAuthenticatorStandAlone::~PAMBasicAuthenticatorStandAlone()
   {
       PEG_METHOD_ENTER(TRC_AUTHENTICATION,
           "PAMBasicAuthenticatorStandAlone::~PAMBasicAuthenticatorStandAlone()");
   
       PEG_METHOD_EXIT();
   }
   
   Boolean PAMBasicAuthenticatorStandAlone::authenticate(
       const String& userName,
       const String& password)
   {
       PEG_METHOD_ENTER(TRC_AUTHENTICATION,
           "PAMBasicAuthenticatorStandAlone::authenticate()");
   
       Boolean authenticated;
       char    auth_reply[10];
       char    line[BUFFERLEN];
       int     n, ret_code;
   
       try
       {
               // Callout to stand-alone process replaces above call...
   
               // Send over the username ...
               CString copy_of_userName=userName.getCString();
               n = strlen(copy_of_userName);
               sprintf(line, "%4u%s", n, (const char*)copy_of_userName);
               n = strlen(line);
               ret_code = write(fd_1[1], line, n);
               if (ret_code != n)
               {
                   continue_PAMauthentication = false;
                   if (printed_err_since_success == false)
                   {
                       printed_err_since_success = true;
                       Logger::put(Logger::ERROR_LOG, "CIMServer",
                          Logger::SEVERE,
                          "Error processing PAM Authentication request (write).");
                   }
                   //
                   // on EPIPE, try restarting the authentication process
                   //
                   if (errno == EPIPE)
                   {
                     close(fd_1[1]);   // Close to keep used fd number down
                     close(fd_2[0]);   // Close to keep used fd number down
                     _createPAMStandalone();
                     ret_code = write(fd_1[1], line, n);
                     if (ret_code != n)
                     {
                       continue_PAMauthentication = false;
                       if (printed_err_since_success == false)
                       {
                         printed_err_since_success = true;
                         //L10N TODO
                         Logger::put(Logger::ERROR_LOG, "CIMServer",
                           Logger::SEVERE,
                           "Error processing PAM Authentication request (write).");
                       }
                     }
                   }  //  if (errno == EPIPE)
               } // if (ret_code != n) from 1st write
   
               // Send over the password ... */
               if (continue_PAMauthentication)
               {
                   CString copy_of_password = password.getCString();
                   n = strlen(copy_of_password);
                   sprintf(line, "%4u%s", n, (const char*)copy_of_password);
                   n = strlen(line);
                   if (write(fd_1[1], line, n) != n) {
                       continue_PAMauthentication = false;
                       if (printed_err_since_success == false)
                       {
                         printed_err_since_success = true;
                         //L10N TODO
                         Logger::put(Logger::ERROR_LOG, "CIMServer",
                           Logger::SEVERE,
                           "Error processing PAM Authentication request (write).");
                       }
                   }
               }
   
               // Now read back the PAM Authentication status value (T/F)
               if (continue_PAMauthentication)
               {
                   n = read(fd_2[0], auth_reply, 2);  /* read back the reply */
   
                   if (n < 0)
                   {
                       continue_PAMauthentication = false;
                       if (printed_err_since_success == false)
                       {
                         printed_err_since_success = true;
                         //L10N TODO
                         Logger::put(Logger::ERROR_LOG, "CIMServer",
                           Logger::SEVERE,
                           "Error processing PAM Authentication request (read).");
                       }
                   }
                   else
                   {
                       auth_reply[n] = '\0';
                   }
               }
   
               authenticated = false;
               if ((continue_PAMauthentication) && (auth_reply[0] == 'T'))
               {
                   authenticated = true;
                   printed_err_since_success = false;
               }
       }
       catch (...)
       {
            throw;
       }
   
       PEG_METHOD_EXIT();
       return (authenticated);
   }
   
   
   void PAMBasicAuthenticatorStandAlone::_createPAMStandalone()
   {
       pid_t   pid;
   
       continue_PAMauthentication = true;
       if (pipe(fd_1) < 0)   // Pipe to write to authentication proc
       {
           continue_PAMauthentication = false;
           if (printed_err_since_success == false)
           {
               printed_err_since_success = true;
               //L10N TODO
               Logger::put(Logger::ERROR_LOG, "CIMServer",
                     Logger::SEVERE,
                     "Error processing PAM Authtication request (pipe).");
           }
       }
       if (continue_PAMauthentication)
       {
           if (pipe(fd_2) < 0)   // Pipe to read from the authentication proc
           {
               continue_PAMauthentication = false;
               if (printed_err_since_success == false)
               {
                   printed_err_since_success = true;
                   //L10N TODO
                   Logger::put(Logger::ERROR_LOG, "CIMServer",
                         Logger::SEVERE,
                         "Error processing PAM Authentication request (pipe).");
               }
           }
       }
       if (continue_PAMauthentication)
       {
           SignalHandler::ignore(PEGASUS_SIGCHLD);  // Allows child death
           if ((pid = fork()) < 0)
           {
               continue_PAMauthentication = false;
               if (printed_err_since_success == false)
               {
                   printed_err_since_success = true;
                   //L10N TODO
                   Logger::put(Logger::ERROR_LOG, "CIMServer",
                         Logger::SEVERE,
                         "Error processing PAM Authentication request (fork).");
               }
           }
           else if (pid > 0)       // This is the PARENT side of the fork
           {
               close(fd_1[0]);     // close read end on 1st pipe
               close(fd_2[1]);     // close write end on 2nd pipe
           }
           else                     // This is the CHILD side of the fork
           {
               close(fd_1[1]);      // close write end on 1st pipe
               close(fd_2[0]);      // close read end on 2nd pipe
               if (fd_1[0] != STDIN_FILENO)
               {
                   if (dup2(fd_1[0], STDIN_FILENO) == -1)
                   {
                       continue_PAMauthentication = false;
                       if (printed_err_since_success == false)
                       {
                           printed_err_since_success = true;
                           //L10N TODO
                           Logger::put(Logger::ERROR_LOG, "CIMServer",
                                 Logger::SEVERE,
                                 "Error processing PAM Authentication request (dup2).");
                       }
                   }
                   close(fd_1[0]);  // don't need this after dup2
               }
               if (continue_PAMauthentication)
               {
                   if (fd_2[1] != STDOUT_FILENO)
                   {
                       if (dup2(fd_2[1], STDOUT_FILENO) == -1)
                       {
                           continue_PAMauthentication = false;
                           if (printed_err_since_success == false)
                           {
                               printed_err_since_success = true;
                               //L10N TODO
                               Logger::put(Logger::ERROR_LOG, "CIMServer",
                                   Logger::SEVERE,
                                   "Error processing PAM Authentication request (dup2).");
                           }
                       }
                       close(fd_2[1]);   // don't need this after dup2
                   }
                   if (continue_PAMauthentication)
                   {
                       //
                       // Get environment variables:
                       //
                       const char* pegasusHome = getenv("PEGASUS_HOME");
   
                       String certpath = FileSystem::getAbsolutePath(
                           pegasusHome, PEGASUS_PAM_STANDALONE_PROC_NAME);
                       if (execl((const char*)certpath.getCString(),
                                 (const char*)certpath.getCString(), (char*)0) < 0)
                       {
                           continue_PAMauthentication = false;
                           if (printed_err_since_success == false)
                           {
                               printed_err_since_success = true;
                               //L10N TODO
                               Logger::put(Logger::ERROR_LOG, "CIMServer",
                                   Logger::SEVERE,
                                   "Error creating PAM Authentication process (execl).");
                           }
                           exit(0);
                       }
                   }
               }
           }
       }
   }
   
   #endif  /* if defined(PEGASUS_OS_HPUX) || ... */
   
   
   
   
   
 PEGASUS_NAMESPACE_END PEGASUS_NAMESPACE_END
   


Legend:
Removed from v.1.4  
changed lines
  Added in v.1.14.4.1

No CVS admin address has been configured
Powered by
ViewCVS 0.9.2