/* pam_checkpassword.c
 * 
 *	The module bit of pamcheck - this reads fd3 and squirts username and
 *		password into PAM_USER and PAM_AUTHTOK.
 *  I spent two days trying to shoehorn this into the application. :/
 *
 * Revision history? :)  0.2
 *
 * Revision 0.3 Added set_maildir option: users with @ in the name get an
 *	envvar MAILDIR set to `.'; anyone else gets MAILDIR `Maildir'. This is
 *  complemented in checkpam, which replaces a first argument of Maildir
 *  with this envvar.
 * Revision 0.2 Added trunc_at option, for future cleverness with virtual
 *  hosts (permits email names as pop user names; anything after @ is ignored)
 *
 * Licensed under the GPL. <www.gnu.org>
 *
 * Revision 0.1 Initial release
 */


/* By Tim Baverstock <warwick@sable.demon.co.uk>
 * 5 March 1997
 *
 * Stuff stolen from pam_rootok and pam_listfile
 */

#include <stdio.h>
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <syslog.h>
#include <pwd.h>
#include <sys/types.h>

#ifndef TRUE
#define TRUE  1L
#define FALSE 0L
#endif

#if 0
#define D(func) {func;}
#else
#define D(func) {;}
#endif

/*
 * here, we make a definition for the externally accessible function
 * in this file (this definition is required for static a module
 * but strongly encouraged generally) it is used to instruct the
 * modules include file to define the function prototypes.
 */

#define PAM_SM_AUTH
/* #define PAM_SM_ACCOUNT */
/* #define PAM_SM_SESSION */
/* #define PAM_SM_PASSWORD */

#include <security/pam_modules.h>

/*---------------------------------------------------------------------*/

#define MODULE_NAME     "pam_checkpassword"

/*---------------------------------------------------------------------*/

/* some syslogging */

static void _pam_log(int err, const char *format, ...)
{
    va_list args;
    va_start(args, format);

    openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH);
    vsyslog(err, format, args);
    closelog();
    D(vfprintf(stderr, format, args));
}

/*---------------------------------------------------------------------*/

/* --- PAM bits --- */

#define PAM_FUNCTION(name) \
 PAM_EXTERN int name (pam_handle_t *pamh,int flags,int argc,const char **argv)

#if 0
#define RETURN_ERROR(i) return ((fail_on_error)?(i):(PAM_SUCCESS))
#else
#define RETURN_ERROR(i) return (i)
#endif

/*---------------------------------------------------------------------*/

/* --- authentication management functions (only) --- */

#ifdef PAM_SM_AUTH

PAM_FUNCTION( pam_sm_authenticate ) {

  char trunc_at=FALSE, set_maildir=FALSE;
  short uname_len=0;

  /* We have an option! :) */

  {
    for ( ; argc-- > 0; ++argv ) {

      /* generic options.. um, ignored. :] */
      
      if ( ! strcmp( *argv, "set_maildir" ) ) {
        set_maildir = TRUE;
      }
      else if ( ! strcmp( *argv, "trunc_at" ) ) {
        trunc_at = TRUE;
      }
      else {
        _pam_log(LOG_ERR, MODULE_NAME ": unknown option; %s",*argv);
      }
    } /* for() */
  }

  /* Just read fd3 and set PAM_USER and PAM_AUTHTOK. Then run away. */

  /* Problem that this doesn't set UID, and that a popuser probably won't
   *  even have one!
   */

  D(_pam_log(LOG_WARNING, "entry"));

  {
	char up[512];     /* Magic 512 defined in checkpassword document */
	int r;
	int uplen = 0;
	for (;;)
	  {
		do {
		  r = read(3,up + uplen,sizeof(up) - uplen);
		} while ((r == -1) && (errno == EINTR));
		if (r == -1) {
		  _pam_log(LOG_WARNING, "interrupted read() on fd3");
		  RETURN_ERROR(PAM_AUTH_ERR);
		}
		if (r == 0) break;
		uplen += r;
		if (uplen >= sizeof(up)) {
		  _pam_log(LOG_WARNING, "datablock too long on fd3");
		  RETURN_ERROR(PAM_AUTH_ERR);
		}
	  }
	
	close(3);

	/* Must have username and password.. */

	for ( r=0; up[r] && r<uplen; r++ );
	for ( r=0; up[r] && r<uplen; r++ );
	if ( r == uplen ) {
	  _pam_log(LOG_WARNING, "username or password unterminated or too long");
	  RETURN_ERROR(PAM_AUTH_ERR);
	}

	uname_len=strlen(up);

	if ( trunc_at || set_maildir ) {
		char *p=up;
		while ( *p && *p!='@' ) p++;
		if ( set_maildir ) {
			if ( *p=='@' ) 
  				r=pam_putenv(pamh, "MAILDIR=.");
			else
				r=pam_putenv(pamh, "MAILDIR=Maildir");
			if ( r != PAM_SUCCESS ) {
	  			_pam_log(LOG_WARNING, "Couldn't set $MAILDIR");
	  			RETURN_ERROR( r );
			}
		}
		if ( trunc_at ) *p=0;
	}

    r=*up && pam_set_item ( pamh, PAM_USER, up );
    if ( r != PAM_SUCCESS ) {
	  _pam_log(LOG_WARNING, "Couldn't set PAM_USER");
	  RETURN_ERROR( r );
	}

    r=pam_set_item ( pamh, PAM_AUTHTOK, up+uname_len+1 );
    if ( r != PAM_SUCCESS ) {
	  _pam_log(LOG_WARNING, "Couldn't set PAM_AUTHTOK");
	  RETURN_ERROR( r );
	}

	_pam_log(LOG_INFO, "Injected user '%s' with password",up);

  }
  D(_pam_log(LOG_WARNING, "exitok"));

  return PAM_SUCCESS;
} 

/* --- Seems to need this function. Ho hum. --- */

PAM_FUNCTION( pam_sm_setcred ) {
  return PAM_SUCCESS; 
}

#endif

/*---------------------------------------------------------------------*/

/* --- session management functions (only) --- */

#ifdef PAM_SM_SESSION

/* To maintain a balance-tally of successful login/outs */

PAM_FUNCTION( pam_sm_open_session ) {
}

PAM_FUNCTION( pam_sm_close_session ) {
}

#endif

/*---------------------------------------------------------------------*/

/* --- authentication management functions (only) --- */

#ifdef PAM_SM_AUTH

PAM_FUNCTION( pam_sm_acct_mgmt ) {
  return PAM_SUCCESS;
} 

#endif  /* #ifdef PAM_SM_AUTH */

/*-----------------------------------------------------------------------*/

#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_listfile_modstruct = {
     MODULE_NAME,
#ifdef PAM_SM_AUTH
     pam_sm_authenticate,
     pam_sm_setcred,
#else
     NULL,
     NULL,
#endif
#ifdef PAM_SM_ACCOUNT
     pam_sm_acct_mgmt,
#else
     NULL,
#endif
#ifdef PAM_SM_SESSION
     pam_sm_open_session,
     pam_sm_close_session,
#else
     NULL,
     NULL,
#endif
#ifdef PAM_SM_PASSWORD
     pam_sm_chauthtok,
#else
     NULL,
#endif
};

#endif   /* #ifdef PAM_STATIC */

/*-----------------------------------------------------------------------*/

