/* pam_rsbac module */

/*
 * $Id: pam_permit.c,v 1.2 2000/12/04 19:02:34 baggins Exp $
 * Written by Andrew Morgan <morgan@parc.power.net> 1996/3/11
 * Modified for rsbac by Amon Ott <ao@rsbac.org> 2005
 * Modified for rsbac by Guillaume Destuynder <kang@rsbac.org> 2006
 *
 * Several functions and some other code copied from 
 * Copyright (c) Jan Rêkorajski 1999.
 * Copyright (c) Andrew G. Morgan 1996-8.
 * Copyright (c) Alex O. Yuriev, 1996.
 * Copyright (c) Cristian Gafton 1996.
 *
 * Modified and glued together for RSBAC authentication by
 * Amon Ott <ao@rsbac.org>
 * Copyright (c) Amon Ott, 2004
 *
 * This product may be distributed under the terms of
 * the GNU Public License.
 *
 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#define DEFAULT_USER "nobody"

#define MISTYPED_PASS _("Sorry, passwords do not match!")

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <utmp.h>
#include <sys/types.h>
#include <sys/syslog.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <rsbac/types.h>
#include <rsbac/syscalls.h>

#include "nls.h"

/*
 * here, we make definitions for the externally accessible functions
 * in this file (these definitions are required for static modules
 * but strongly encouraged generally) they are used to instruct the
 * modules include file to define their prototypes.
 */

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

#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <rsbac/types.h>
#include <rsbac/syscalls.h>

#define _pam_delete(xx)		\
{				\
	_pam_overwrite(xx);	\
	_pam_drop(xx);		\
}

void _log_err(int err, pam_handle_t *pamh, const char *format,...)
{
	va_list args;
	const char tag[] = "(pam_rsbac) ";
	char *mod_format;
	int free_mod_format = 1;

	mod_format = malloc( 1 + sizeof(tag) + strlen(format));
	if(mod_format == NULL) {
	  free_mod_format = 0;
	  mod_format = (char *) format;
	} else {
	  strcpy(mod_format, tag);
	  strcat( mod_format, format);
	}

	va_start(args, format);
	vsyslog(err | LOG_AUTH, mod_format, args);
	va_end(args);

	if (free_mod_format) free(mod_format);
}

static int converse(pam_handle_t * pamh, int ctrl, int nargs
		    ,struct pam_message **message
		    ,struct pam_response **response)
{
	int retval;
	struct pam_conv *conv;

	D(("begin to converse"));

	retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
	if (retval == PAM_SUCCESS) {

		retval = conv->conv(nargs, (const struct pam_message **) message
				    ,response, conv->appdata_ptr);

		D(("returned from application's conversation function"));

		if (retval != PAM_SUCCESS) {
			_log_err(LOG_DEBUG, pamh, "conversation failure [%s]"
				 ,pam_strerror(pamh, retval));
		}
	} else if (retval != PAM_CONV_AGAIN) {
		_log_err(LOG_ERR, pamh
		         ,"couldn't obtain conversation function [%s]"
			 ,pam_strerror(pamh, retval));
	}
	D(("ready to return from module conversation"));

	return retval;		/* propagate error status */
}

int _make_remark(pam_handle_t * pamh, unsigned int ctrl
		       ,int type, const char *text)
{
	int retval = PAM_SUCCESS;

	{
		struct pam_message *pmsg[1], msg[1];
		struct pam_response *resp;
		char remark[RSBAC_MAXNAMELEN];

		sprintf(remark, "pam_rsbac.so: %s", text);
		pmsg[0] = &msg[0];
		msg[0].msg = remark;
		msg[0].msg_style = type;

		resp = NULL;
		retval = converse(pamh, 0, 1, pmsg, &resp);

		if (resp) {
			_pam_drop_reply(resp, 1);
		}
	}
	return retval;
}

  /*
   * Beacause getlogin() is braindead and sometimes it just
   * doesn't work, we reimplement it here.
   */
char *PAM_getlogin(void)
{
	struct utmp *ut, line;
	char *curr_tty, *retval;
	static char curr_user[sizeof(ut->ut_user) + 4];

	retval = NULL;

	curr_tty = ttyname(0);
	if (curr_tty != NULL && (strlen(curr_tty) > 5)) {
		D(("PAM_getlogin ttyname: %s", curr_tty));
		curr_tty += 5;
		setutent();
		strncpy(line.ut_line, curr_tty, sizeof(line.ut_line));
		if ((ut = getutline(&line)) != NULL) {
			strncpy(curr_user, ut->ut_user, sizeof(ut->ut_user));
			curr_user[sizeof(curr_user) - 1] = '\0';
			retval = curr_user;
		}
		endutent();
	}
	if(retval)
          D(("PAM_getlogin retval: %s", retval));

	return retval;
}


/* --- authentication management functions --- */

PAM_EXTERN
int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc
			,const char **argv)
{
    int retval;
    const char *user=NULL;
    char * p;
    unsigned int ctrl = 0;

    /*
     * authentication requires we know who the user wants to be
     */
    retval = pam_get_user(pamh, &user, NULL);
    if (retval != PAM_SUCCESS) {
	D(("get user returned error: %s", pam_strerror(pamh,retval)));
	return retval;
    }
    if (user == NULL || *user == '\0') {
	D(("username not known"));
	pam_set_item(pamh, PAM_USER, (const void *) DEFAULT_USER);
    }
    retval = pam_get_item (pamh, PAM_AUTHTOK, (void *) &p);
    if(retval != PAM_SUCCESS)
      {
	if (retval != PAM_CONV_AGAIN) {
		_log_err(LOG_CRIT, pamh, "auth could not identify password for [%s]"
			 ,user);
	} else {
		D(("conversation function is not ready yet"));
		/*
		 * it is safe to resume this function so we translate this
		 * retval to the value that indicates we're happy to resume.
		 */
		retval = PAM_INCOMPLETE;
	}
	user = NULL;
	return retval;
      }
    if(!p)
      {
	struct pam_message msg[3], *pmsg[3];
	struct pam_response *resp;
	int i, replies;
	char * comment = NULL;
	char prompt1[RSBAC_MAXNAMELEN];
	char * token = NULL;

	/* prepare to converse */
#ifdef ENABLE_RSBAC_PROMPT
        snprintf(prompt1, RSBAC_MAXNAMELEN - 1, _("%s's RSBAC password: "), user);
#else
	snprintf(prompt1, RSBAC_MAXNAMELEN, _("Password:"));
#endif
        prompt1[RSBAC_MAXNAMELEN - 1] = 0;

	if (comment != NULL) {
		pmsg[0] = &msg[0];
		msg[0].msg_style = PAM_TEXT_INFO;
		msg[0].msg = comment;
		i = 1;
	} else {
		i = 0;
	}

	pmsg[i] = &msg[i];
	msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
	msg[i++].msg = prompt1;
	replies = 1;

	/* so call the conversation expecting i responses */
	resp = NULL;
	retval = converse(pamh, ctrl, i, pmsg, &resp);

	if (resp != NULL) {
		/* interpret the response */

		if (retval == PAM_SUCCESS) {	/* a good conversation */

			token = x_strdup(resp[i - replies].resp);
			if (token != NULL) {
                                p = token;
                                pam_set_item (pamh, PAM_AUTHTOK, p);
			} else {
				_log_err(LOG_NOTICE, pamh
					 ,"could not recover authentication token");
			}

		}
		/*
		 * tidy up the conversation (resp_retcode) is ignored
		 * -- what is it for anyway? AGM
		 */

		_pam_drop_reply(resp, i);

	} else {
		retval = (retval == PAM_SUCCESS)
		    ? PAM_AUTHTOK_RECOVER_ERR : retval;
                return retval;
	}
      }
    retval = rsbac_um_auth_name((char *) user, p);
    if(!retval)
      return PAM_SUCCESS;
  switch(errno)
    {
      case RSBAC_EMUSTCHANGE:
        return PAM_SUCCESS;

      case EPERM:
        _log_err(LOG_NOTICE, pamh,
                 "could not authenticate user %s",
                 user);
        pam_set_item (pamh, PAM_AUTHTOK, NULL);
        return PAM_AUTH_ERR;
      case RSBAC_ENOTFOUND:
        _log_err(LOG_NOTICE, pamh,
                 "could not authenticate user %s",
                 user);
        return PAM_USER_UNKNOWN;

      case RSBAC_EINVALIDMODULE:
      case ENOSYS:
        _log_err(LOG_NOTICE, pamh,
                 "RSBAC user management not available");
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("RSBAC user management not available"));
        return PAM_USER_UNKNOWN;

      case RSBAC_EINVALIDVERSION:
        _log_err(LOG_NOTICE, pamh,
                 "Incompatible RSBAC version, this PAM module was compiled for %s",
                 RSBAC_VERSION);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("Incompatible RSBAC version"));
        return PAM_USER_UNKNOWN;

      case RSBAC_EEXPIRED:
        _log_err(LOG_NOTICE, pamh,
                 "account %s has expired (account expired)",
                 user);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                    _("Your account has expired, please contact system administrator"));
        pam_set_item (pamh, PAM_AUTHTOK, NULL);
        return PAM_ACCT_EXPIRED;

      default:
        {
          char tmp[RSBAC_MAXNAMELEN];
          char tmp2[RSBAC_MAXNAMELEN];

          _log_err(LOG_NOTICE, pamh,
                   "error not handled: %s",
                   get_error_name(tmp, retval));
          snprintf(tmp2, RSBAC_MAXNAMELEN - 1, _("Unhandled error %s, please contact system administrator!"),
                   get_error_name(tmp, retval));
          tmp2[RSBAC_MAXNAMELEN - 1] = 0;
          _make_remark(pamh, ctrl, PAM_TEXT_INFO, tmp2);
          return PAM_AUTHINFO_UNAVAIL;
        }
    }
}

PAM_EXTERN
int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc
		   ,const char **argv)
{
     return PAM_SUCCESS;
}

/* --- account management functions --- */

PAM_EXTERN
int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc
		     ,const char **argv)
{
  const char * user;
  int retval;
  unsigned int ctrl = 0;

  retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
  if (retval != PAM_SUCCESS || user == NULL)
    {
      _log_err(LOG_ALERT, pamh
		 ,"could not identify user (from uid=%d)"
		 ,getuid());
      return PAM_USER_UNKNOWN;
    }

  retval = rsbac_um_check_account_name((char *) user);
  if(!retval)
    return PAM_SUCCESS;
  switch(errno)
    {
      case RSBAC_ENOTFOUND:
        _log_err(LOG_NOTICE, pamh,
                 "could not identify user %s",
                 user);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("Cannot lookup your account data"));
        return PAM_USER_UNKNOWN;

      case RSBAC_EINVALIDMODULE:
      case ENOSYS:
        _log_err(LOG_NOTICE, pamh,
                 _("RSBAC user management not available"));
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("RSBAC user management not available"));
        return PAM_USER_UNKNOWN;

      case RSBAC_EINVALIDVERSION:
        _log_err(LOG_NOTICE, pamh,
                 "Incompatible RSBAC version, this PAM module was compiled for %s",
                 RSBAC_VERSION);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("Incompatible RSBAC version"));
        return PAM_USER_UNKNOWN;

      case RSBAC_EMUSTCHANGE:
        _log_err(LOG_NOTICE, pamh,
                 "expired password for user %s",
                 user);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("You are required to change your password immediately"));
        return PAM_NEW_AUTHTOK_REQD;

      case RSBAC_EEXPIRED:
        _log_err(LOG_NOTICE, pamh,
                 "account %s has expired (account expired)",
                 user);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                    _("Your account has expired, please contact system administrator"));
        return PAM_ACCT_EXPIRED;

      default:
        break;
    }
  /* within warning period? */
  if(retval > 0)
    {
      char tmp[RSBAC_MAXNAMELEN];

      _log_err(LOG_DEBUG, pamh
               ,"password for user %s will expire in %d days",
               user, retval);
      snprintf(tmp, RSBAC_MAXNAMELEN - 1, _("Warning: your password will expire in %d day%.2s"),
               retval, retval == 1 ? "" : "s");
      tmp[RSBAC_MAXNAMELEN - 1] = 0;
      _make_remark(pamh, ctrl, PAM_TEXT_INFO, tmp);
      return PAM_SUCCESS;
    }
  /* other error? */
  if(retval < 0)
    {
      char tmp[RSBAC_MAXNAMELEN];
      char tmp2[RSBAC_MAXNAMELEN];

      _log_err(LOG_NOTICE, pamh,
               "error not handled: %s",
               get_error_name(tmp, retval));
      snprintf(tmp2, RSBAC_MAXNAMELEN - 1, _("Unhandled error %s, please contact system administrator!"),
               get_error_name(tmp, retval));
      tmp2[RSBAC_MAXNAMELEN - 1] = 0;
      _make_remark(pamh, ctrl, PAM_TEXT_INFO, tmp2);
      return PAM_AUTHINFO_UNAVAIL;
    }
  /* OK, we got it. */
  return PAM_SUCCESS;
}

/* --- password management --- */

PAM_EXTERN
int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc
		     ,const char **argv)
{
    int retval;
    const char *user=NULL;
    rsbac_uid_t uid = RSBAC_GEN_UID(RSBAC_UM_VIRTUAL_KEEP, RSBAC_NO_USER);
    char * p_old;
    char * p_new;
    unsigned int ctrl = 0;
    struct pam_message msg[3], *pmsg[3];
    struct pam_response *resp;
    int i, replies;
    char prompt1[RSBAC_MAXNAMELEN];
    char prompt2[RSBAC_MAXNAMELEN];
    char * token = NULL;

    snprintf(prompt2, RSBAC_MAXNAMELEN, _("Repeat new password: "));
    prompt2[RSBAC_MAXNAMELEN - 1] = 0;

    retval = pam_get_user(pamh, &user, NULL);
    if (retval != PAM_SUCCESS) {
	D(("get user returned error: %s", pam_strerror(pamh,retval)));
	return retval;
    }
    if(!user)
      return PAM_SERVICE_ERR;
    if(!user[0])
      return PAM_USER_UNKNOWN;

    if (flags & PAM_PRELIM_CHECK)
      {
        retval = rsbac_um_get_uid(0, (char *) user, &uid);
        if(!retval)
          return PAM_SUCCESS;
        switch(errno)
          {
            case RSBAC_EEXPIRED:
              return PAM_ACCT_EXPIRED;
            default:
              return PAM_TRY_AGAIN;
          }
      }
    if (flags & PAM_CHANGE_EXPIRED_AUTHTOK)
      {
        retval = rsbac_um_check_account_name((char *) user);
        if(!retval)
          return PAM_SUCCESS;
        switch(errno)
          {
            case RSBAC_EEXPIRED:
            case RSBAC_EMUSTCHANGE:
              break;
            default:
              return PAM_TRY_AGAIN;
          }
      }

    retval = pam_get_item (pamh, PAM_OLDAUTHTOK, (void *) &p_old);
    if(retval != PAM_SUCCESS || !p_old)
      {
        /* prepare to converse */
        snprintf(prompt1, RSBAC_MAXNAMELEN - 1, _("Old password for user %s: "), user);
        prompt1[RSBAC_MAXNAMELEN - 1] = 0;
        i = 0;

        pmsg[i] = &msg[i];
        msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
        msg[i++].msg = prompt1;
        replies = 1;
        /* so call the conversation expecting i responses */
        resp = NULL;
        retval = converse(pamh, ctrl, i, pmsg, &resp);

        if (resp != NULL) {
                /* interpret the response */
                if (retval == PAM_SUCCESS) {        /* a good conversation */

                        token = x_strdup(resp[i - replies].resp);
                        if (token != NULL) {
                                    p_old = token;
                        } else {
                                _log_err(LOG_NOTICE, pamh
                                         ,"could not recover authentication token");
                        }
                }
                /*
                 * tidy up the conversation (resp_retcode) is ignored
                 * -- what is it for anyway? AGM
                 */

                _pam_drop_reply(resp, i);
                if(!token)
                  return PAM_AUTHTOK_RECOVER_ERR;

        } else {
                retval = (retval == PAM_SUCCESS)
                    ? PAM_AUTHTOK_RECOVER_ERR : retval;
                    return retval;
        }
        retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) p_old);
        if (retval != PAM_SUCCESS) {
                _log_err(LOG_CRIT, pamh,
                         "failed to set PAM_OLDAUTHTOK");
        }
      }
    retval = pam_get_item (pamh, PAM_AUTHTOK, (void *) &p_new);
    if(retval != PAM_SUCCESS || !p_new)
      {
        /* prepare to converse */
        snprintf(prompt1, RSBAC_MAXNAMELEN - 1, _("New password for user %s: "), user);
        prompt1[RSBAC_MAXNAMELEN - 1] = 0;
        i = 0;
    
        pmsg[i] = &msg[i];
        msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
        msg[i++].msg = prompt1;
        pmsg[i] = &msg[i];
        msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
        msg[i++].msg = prompt2;
        replies = 2;
    
        /* so call the conversation expecting i responses */
        resp = NULL;
        retval = converse(pamh, ctrl, i, pmsg, &resp);
    
        if (resp != NULL) {
                /* interpret the response */
    
                if (retval == PAM_SUCCESS) {        /* a good conversation */
    
                        token = x_strdup(resp[i - replies].resp);
                        if (token != NULL) {
                                        /* verify that password entered correctly */
                                        if (!resp[i - 1].resp
                                            || strcmp(token, resp[i - 1].resp)) {
                                                _pam_delete(token);        /* mistyped */
                                                retval = PAM_AUTHTOK_RECOVER_ERR;
                                                _make_remark(pamh, ctrl
                                                            ,PAM_ERROR_MSG, MISTYPED_PASS);
                                                _pam_drop_reply(resp, i);
                                                return retval;
                                        }
                                    p_new = token;
                        } else {
                                _log_err(LOG_NOTICE, pamh
                                         ,"could not recover authentication token");
                        }
    
                }
                /*
                 * tidy up the conversation (resp_retcode) is ignored
                 * -- what is it for anyway? AGM
                 */
    
                _pam_drop_reply(resp, i);
                if(!token)
                  return PAM_AUTHTOK_RECOVER_ERR;
    
        } else {
                retval = (retval == PAM_SUCCESS)
                    ? PAM_AUTHTOK_RECOVER_ERR : retval;
                    return retval;
        }
      }
    if(user)
      {
        retval = rsbac_um_set_pass_name((char *) user, p_old, p_new);
      }
    else
      {
        uid = RSBAC_GEN_UID(RSBAC_UM_VIRTUAL_KEEP, getuid());
        retval = rsbac_um_set_pass(uid, p_old, p_new);
      }
    
  if(!retval)
    return PAM_SUCCESS;
  switch(errno)
    {
      case EPERM:
        _log_err(LOG_NOTICE, pamh,
                 "could not authenticate user %s",
                 user);
        return PAM_AUTH_ERR;
      case RSBAC_ENOTFOUND:
        _log_err(LOG_NOTICE, pamh,
                 "could not authenticate user %s",
                 user);
        return PAM_USER_UNKNOWN;
      case RSBAC_EWEAKPASSWORD:
        _log_err(LOG_NOTICE, pamh,
                 "new password for user %s is too weak",
                 user);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("New password is too weak"));
        return PAM_TRY_AGAIN;

      case RSBAC_EINVALIDMODULE:
      case ENOSYS:
        _log_err(LOG_NOTICE, pamh,
                 "RSBAC user management not available");
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("RSBAC user management not available"));
        return PAM_USER_UNKNOWN;

      case RSBAC_EINVALIDVERSION:
        _log_err(LOG_NOTICE, pamh,
                 "Incompatible RSBAC version, this PAM module was compiled for %s",
                 RSBAC_VERSION);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                     _("Incompatible RSBAC version"));
        return PAM_USER_UNKNOWN;

      case RSBAC_EEXPIRED:
        _log_err(LOG_NOTICE, pamh,
                 "account %s has expired (account expired)",
                 user);
        _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                    _("Your account has expired, please contact system administrator"));
        return PAM_ACCT_EXPIRED;

      default:
        {
          char tmp[RSBAC_MAXNAMELEN];
          char tmp2[RSBAC_MAXNAMELEN];

          _log_err(LOG_NOTICE, pamh,
                   "error not handled: %s",
                   get_error_name(tmp, retval));
          snprintf(tmp2, RSBAC_MAXNAMELEN - 1, _("Unhandled error %s, please contact system administrator!"),
                   get_error_name(tmp, retval));
          tmp2[RSBAC_MAXNAMELEN - 1] = 0;
          _make_remark(pamh, ctrl, PAM_TEXT_INFO, tmp2);
          return PAM_AUTHINFO_UNAVAIL;
        }
    }
}

/* --- session management --- */

PAM_EXTERN
int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc
                        ,const char **argv)
{
  char *user_name, *service, *login;
  int retval;

  retval = pam_get_item(pamh, PAM_USER, (void *) &user_name);
  if (user_name == NULL || retval != PAM_SUCCESS)
    {
      _log_err(LOG_CRIT, pamh,
               "open_session - error recovering username");
      return PAM_SESSION_ERR;		/* How did we get authenticated with
						   no username?! */
    }
  retval = pam_get_item(pamh, PAM_SERVICE, (void *) &service);
  if (service == NULL || retval != PAM_SUCCESS)
    {
      _log_err(LOG_CRIT, pamh,
               "open_session - error recovering service");
      return PAM_SESSION_ERR;
    }
  login = PAM_getlogin();
  _log_err(LOG_INFO, pamh, "session opened for user %s by %s(uid=%d)",
           user_name,
           login == NULL ? "*unknown*" : login, getuid());

  return PAM_SUCCESS;
}

PAM_EXTERN
int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
			 ,const char **argv)
{
  char *user, *service;
  int retval;

  retval = pam_get_item(pamh, PAM_USER, (void *) &user);
  if (user == NULL || retval != PAM_SUCCESS)
    {
      _log_err(LOG_CRIT, pamh,
               "close_session - error recovering username");
      return PAM_SESSION_ERR;		/* How did we get authenticated with
						   no username?! */
    }
  retval = pam_get_item(pamh, PAM_SERVICE, (void *) &service);
  if (service == NULL || retval != PAM_SUCCESS)
    {
      _log_err(LOG_CRIT, pamh,
               "close_session - error recovering service");
      return PAM_SESSION_ERR;
    }
  _log_err(LOG_INFO, pamh, "session closed for user %s",
           user);

  return PAM_SUCCESS;
}

/* end of module definition */

#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_permit_modstruct = {
    "pam_permit",
    pam_sm_authenticate,
    pam_sm_setcred,
    pam_sm_acct_mgmt,
    pam_sm_open_session,
    pam_sm_close_session,
    pam_sm_chauthtok
};

#endif
