/* ========================================================================
 * Copyright 1988-2006 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 
 * ========================================================================
 */

/*
 * Program:	Pluggable Authentication Modules login services
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	1 August 1988
 * Last Edited:	31 August 2006
 */


#ifdef MAC_OSX_KLUDGE		/* why can't Apple be compatible? */
#include <pam/pam_appl.h>
#else
#include <security/pam_appl.h>
#endif

struct checkpw_cred {
  char *uname;			/* user name */
  char *pass;			/* password */
};

/* PAM conversation function
 * Accepts: number of messages
 *	    vector of messages
 *	    pointer to response return
 *	    application data
 * Returns: PAM_SUCCESS if OK, response vector filled in, else PAM_CONV_ERR
 */

static int checkpw_conv (int num_msg,const struct pam_message **msg,
			 struct pam_response **resp,void *appdata_ptr)
{
  int i;
  struct checkpw_cred *cred = (struct checkpw_cred *) appdata_ptr;
  struct pam_response *reply = fs_get (sizeof (struct pam_response) * num_msg);
  for (i = 0; i < num_msg; i++) switch (msg[i]->msg_style) {
  case PAM_PROMPT_ECHO_ON:	/* assume want user name */
    reply[i].resp_retcode = PAM_SUCCESS;
    reply[i].resp = cpystr (cred->uname);
    break;
  case PAM_PROMPT_ECHO_OFF:	/* assume want password */
    reply[i].resp_retcode = PAM_SUCCESS;
    reply[i].resp = cpystr (cred->pass);
    break;
  case PAM_TEXT_INFO:
  case PAM_ERROR_MSG:
    reply[i].resp_retcode = PAM_SUCCESS;
    reply[i].resp = NULL;
    break;
  default:			/* unknown message style */
    fs_give ((void **) &reply);
    return PAM_CONV_ERR;
  }
  *resp = reply;
  return PAM_SUCCESS;
}


/* PAM cleanup
 * Accepts: handle
 */

static void checkpw_cleanup (pam_handle_t *hdl)
{
#if 0	/* see checkpw() for why this is #if 0 */
  pam_close_session (hdl,NIL);	/* close session [uw]tmp */
#endif
  pam_setcred (hdl,PAM_DELETE_CRED);
  pam_end (hdl,PAM_SUCCESS);
}

/* Server log in
 * Accepts: user name string
 *	    password string
 * Returns: T if password validated, NIL otherwise
 */

struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
{
  pam_handle_t *hdl;
  struct pam_conv conv;
  struct checkpw_cred cred;
  char *name = cpystr (pw->pw_name);
  conv.conv = &checkpw_conv;
  conv.appdata_ptr = &cred;
  cred.uname = name;
  cred.pass = pass;
  if (pw = ((pam_start ((char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
			pw->pw_name,&conv,&hdl) == PAM_SUCCESS) &&
	    (pam_set_item (hdl,PAM_RHOST,tcp_clientaddr ()) == PAM_SUCCESS) &&
	    (pam_authenticate (hdl,NIL) == PAM_SUCCESS) &&
	    (pam_acct_mgmt (hdl,NIL) == PAM_SUCCESS) &&
	    (pam_setcred (hdl,PAM_ESTABLISH_CRED) == PAM_SUCCESS)) ?
      getpwnam (name) : NIL) {
#if 0
    /*
     * Some people have reported that this causes a SEGV in strncpy() from
     * pam_unix.so.1
     */
    /*
     * This pam_open_session() call is inconsistant with how we handle other
     * platforms, where we don't write [uw]tmp records.  However, unlike our
     * code on other platforms, pam_acct_mgmt() will check those records for
     * inactivity and deny the authentication.
     */
    pam_open_session (hdl,NIL);	/* make sure account doesn't go inactive */
#endif
				/* arm hook to delete credentials */
    mail_parameters (NIL,SET_LOGOUTHOOK,(void *) checkpw_cleanup);
    mail_parameters (NIL,SET_LOGOUTDATA,(void *) hdl);
  }
  else checkpw_cleanup (hdl);	/* clean up */
  fs_give ((void **) &name);
				/* reset log facility in case PAM broke it */
  if (myServerName) openlog (myServerName,LOG_PID,syslog_facility);
  return pw;
}
