/***********************************************************************
 *  File: pam_authtoken.c
 *  Author: Juan Carlos Luciani (jluciani@novell.com)
 * 
 *  Abstract: Implements the CASA Authentication Token PAM Module.
 * 
 *  Copyright (C) 2005 Novell, Inc.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com. 
 ***********************************************************************/

#define _GNU_SOURCE

#include <stdarg.h>
#include <syslog.h>

#ifndef LINUX 
#include <security/pam_appl.h>
#endif

#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_PASSWORD
#define PAM_SM_SESSION

#include <security/pam_modules.h>
#include <security/_pam_macros.h>

#include <casa_auth_token.h>


/* ************************************************************************
 * LogError()
 *
 * Logs error to syslog.
 *
 * ************************************************************************/
static void
LogError(char *pFormatStr, ... )
{
   va_list  args;

//   openlog("pam_authtoken", LOG_CONS | LOG_NOWAIT | LOG_ODELAY, LOG_USER);
   va_start(args, pFormatStr);

// vsyslog(LOG_USER | LOG_INFO, pFormatStr, args);
   printf(pFormatStr, args);

   va_end(args);
//   closelog();
}


/* ************************************************************************
 * pam_sm_authenticate()
 *
 * Service provider implementation for pam_authenticate().
 *
 * This is a PAM authentication management function.
 *
 * We are going to validate the credentials using the CASA Authentication
 * Token Credential APIs.
 *
 * ************************************************************************/
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh,
                    int flags,
                    int argc,
                    const char **argv)
{
   int         retStatus;
   CasaStatus  casaStatus;
   char        *pServicename = NULL;
   char        *pUsername = NULL;
   char        *pAuthToken = NULL;


   // Get the servicename.
   if (pam_get_item(pamh, PAM_SERVICE, (void*) &pServicename) == PAM_SUCCESS
       && pServicename != NULL)
   {
      // We got the service name, now obtain the username.
      // Note that we are not calling pam_get_user() because we
      // assume that the service has set it before calling PAM_Authenticate.
      if (pam_get_item(pamh, PAM_USER, (void*) &pUsername) == PAM_SUCCESS
          && pUsername != NULL)
      {
         struct pam_response  *responses = NULL;

         // We got the username, now obtain the authentication token.
         if (pam_get_item(pamh, PAM_AUTHTOK, (void*) &pAuthToken) != PAM_SUCCESS
             || pAuthToken == NULL)
         {
            struct pam_conv *pConv;

            // The authentication token has not been set, try to obtain it from the
            // application through the use of the conversation function.
            if (pam_get_item(pamh, PAM_CONV, (void*) &pConv) == PAM_SUCCESS)
            {
               struct pam_message   msg;
               struct pam_message   *messages = &msg;

               // Obtained the conversation structure, now query the conversation
               // function for the authentication token.
               msg.msg_style = PAM_PROMPT_ECHO_OFF;
               if (pConv->conv(1,
                               (const struct pam_message **) &messages,
                               &responses,
                               pConv->appdata_ptr) == PAM_SUCCESS)
               {
                  // Check if we have a successful response
                  if (responses[0].resp_retcode == PAM_SUCCESS
                      && responses[0].resp)
                  {
                     // Set the authentication token with PAM
                     if (pam_set_item(pamh, PAM_AUTHTOK, responses[0].resp) == PAM_SUCCESS)
                     {
                        // Use the buffer returned by the caller as the authentication token
                        pAuthToken = responses[0].resp;
                     }
                     else
                     {
                        LogError("Unable to set the authentication token");
                     }
                  }
                  else
                  {
                     LogError("Response error");
                  }
               }
               else
               {
                  LogError("Conversation function error");
               }
            }
            else
            {
               LogError("Unable to obtain conversation structure");
            }
         }

         // Check if we suuceeded at obtaining the authentication token
         if (pAuthToken)
         {
            // We got all of the information that we need, now validate the credentials.
            casaStatus = ValidateAuthTokenCredentials(pServicename,
                                                      pUsername,
                                                      strlen(pUsername),
                                                      pAuthToken,
                                                      strlen(pAuthToken));
            if (CASA_SUCCESS(casaStatus))
            {
               // Authentication succeded
               retStatus = PAM_SUCCESS;
            }
            else
            {
               LogError("Service %s failed to authenticate %s with status = %08X", pServicename, pUsername, casaStatus);
               retStatus = PAM_AUTH_ERR;
            }
         }
         else
         {
            LogError("Unable to obtain authentication token");
            retStatus = PAM_CRED_INSUFFICIENT;
         }

         // Free conversation function response buffers if necessary
         if (responses)
         {
            if (responses[0].resp)
               free(responses[0].resp);
            free(responses);
         }
      }
      else
      {
         LogError("Unable to obtain username");
         retStatus = PAM_CRED_INSUFFICIENT;
      }
   }
   else
   {
      LogError("Unable to obtain servicename");
      retStatus = PAM_SYSTEM_ERR;
   }

   return retStatus;
}


/* ************************************************************************
 * pam_sm_setcred()
 *
 * Service provider implementation for pam_setcred().
 *
 * This is a PAM authentication management function.
 *
 * This function is here just for completedness and to protect against
 * PAM misconfiguration.
 *
 * ************************************************************************/
PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh,
               int flags,
               int argc,
               const char **argv)
{
   return PAM_SUCCESS;
}


/* ************************************************************************
 * pam_sm_acct_mgmt()
 *
 * Service provider implementation for pam_acct_mgmt().
 *
 * This is a PAM account management function.
 *
 * This function is here just for completedness and to protect against
 * PAM misconfiguration.
 *
 * ************************************************************************/
PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t *pamh,
                 int flags,
                 int argc,
                 const char **argv)
{
   return PAM_SUCCESS;
}


/* ************************************************************************
 * pam_sm_chauthtok()
 *
 * Service provider implementation for pam_chauthtok().
 *
 * This is a PAM password management function.
 *
 * This function is here just for completedness and to protect against
 * PAM misconfiguration.
 *
 * ************************************************************************/
PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh,
                 int flags,
                 int argc,
                 const char **argv)
{
   return PAM_SUCCESS;
}


/* ************************************************************************
 * pam_sm_open_session()
 *
 * Service provider implementation for pam_open_session().
 *
 * This is a PAM session management function.
 *
 * This function is here just for completedness and to protect against
 * PAM misconfiguration.
 *
 * ************************************************************************/
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh,
                                   int flags,
                                   int argc,
                                   const char **argv)
{
   return PAM_SUCCESS;
}


/* ************************************************************************
 * pam_sm_close_session()
 *
 * Service provider implementation for pam_close_session().
 *
 * This is a PAM session management function.
 *
 * This function is here just for completedness and to protect against
 * PAM misconfiguration.
 *
 * ************************************************************************/
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh,
                                    int flags,
                                    int argc,
                                    const char **argv)
{
   return PAM_SUCCESS;
}


/* static module data */
#ifdef PAM_STATIC
struct pam_module _pam_pwcapture_modstruct = {
   "pam_pwcapture",
   pam_sm_authenticate,
   pam_sm_setcred,
   pam_sm_acct_mgmt,
   pam_sm_chauthtok,
   pam_sm_open_session,
   pam_sm_close_session
};
#endif