/* PAM to automate Uni-Klu login tasks */

/* 
 * Albrecht Gebhardt <agebhard@uni-klu.ac.at>
 * Copyright: GPL, See COPYING.LIB-2.0
 *
 * Created Sun Jun 16 21:12:20 CEST 2002
 * 
 * Version:
 * $Id: pam_linux_uniklu_auth.c,v 1.11 2009-01-11 15:46:16 agebhard Exp $
 * 
 * Last Revised on
 * $Date: 2009-01-11 15:46:16 $
 * 
 * Changelog:
 *
 * $Log: pam_linux_uniklu_auth.c,v $
 * Revision 1.11  2009-01-11 15:46:16  agebhard
 * remove unused vars
 *
 * Revision 1.10  2009-01-09 10:17:09  agebhard
 * ...
 *
 * Revision 1.9  2004/10/30 18:07:25  agebhard
 * docu reorganized
 * ignore_root works now
 *
 * Revision 1.8  2004/10/30 16:32:50  agebhard
 * fixed kdm crash in close_session
 * fixed wrong password length in kdm session
 * added more expect scripts, untested
 *
 * Revision 1.7  2004/10/29 22:49:59  agebhard
 * sort of works now also for kdm
 * short passwords are read back with extra characters, leads to wrong degarble
 *
 * Revision 1.6  2004/10/29 21:57:43  agebhard
 * debug fixes
 *
 * Revision 1.5  2004/10/28 21:40:47  agebhard
 * working again (debian sarge)
 *
 * Revision 1.4  2002/06/18 13:25:08  agebhard
 * new debugging. still not working on madrake.
 * pam_sm_authenticate fails because it is called before entereing a password!
 *
 * Revision 1.3  2002/06/17 09:57:28  agebhard
 * more cvs keywords ...
 *
 * 
 */

#define PAM_SM_AUTH

#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <security/pam_modules.h>
#include "pam_linux_uniklu.h"

PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
				   int argc, const char **argv)
{
    const char *procname="pam_sm_authenticate";

    int	i,retval=0,buflen=0;
    char *username=NULL, *password=NULL, *buf=NULL, *local_password=NULL;
    int ignore_root=0, is_root=0, allow_second_try=0;

#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s",procname);
#   endif


    /* get user name */
    retval = pam_get_user(pamh, (void *) &username, NULL);
    if (retval != PAM_SUCCESS) return(retval);

    /* is root logging in? */
    if (strcmp(username, "root") == 0) is_root=1;


    /* handle arguments */
    for (i=0; i<argc; i++)
	{
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: Argument #%d: '%s'", procname, i, argv[i]);
#           endif    
	    /* get uniklu_helper program name (if applicable) */
	    if (strcmp(argv[i], "uniklu_helper_program") == 0)
		{
		    /* next argument is uniklu_helper program name */
		    i++;
		    /* ignored */
		}
	    else if (strcmp(argv[i], "ignore_root") == 0)
		{
		    /* skip root logins */
                    ignore_root=1;
		}	    
	}
    if (is_root && ignore_root)
	{
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: ignore root login",procname);
#           endif    
	    return(PAM_SUCCESS);
	}
    
    /* get password (if not already known) */
    if (password == NULL)
	{
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: get password",procname);
#           endif    
	    allow_second_try = 0;
	    retval = pam_linux_uniklu_get_password(pamh, &password,
						   NULL, 1);
	    if (retval != PAM_SUCCESS){
		syslog(LOG_CRIT, "%s: get password failed: %s",procname, 
		       pam_strerror(pamh,retval));
		return(retval);
	    }
	}
#   ifdef DEBUG
    //syslog(LOG_DEBUG, "%s: got password: %s",procname, PASSWD);
    syslog(LOG_DEBUG, "%s: got password: %s",procname, password);
#   endif    

    /* store password for uniklu pam session 
     * first step: via environment to pam_pm_setcred.
     */
    if((local_password=(char*) malloc(sizeof(char)*strlen(password)+1)) ==NULL) {
	syslog(LOG_CRIT, "%s: Out of memory!\n",procname);
	return(PAM_AUTH_ERR);
    };
    strncpy(local_password,password,strlen(password));
    local_password[strlen(password)]='\0';
    
    retval = pam_linux_uniklu_garble_string(pamh,
    					    local_password, strlen(local_password));
    if (retval != PAM_SUCCESS) return PAM_AUTHINFO_UNAVAIL;
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s: garbled password: %s",procname, LOCAL_PASSWD);
#   endif    
    buflen=strlen(password)+20;
    if((buf=(char*) malloc(sizeof(char)*buflen+1)) ==NULL) {
	syslog(LOG_CRIT, "%s: Out of memory!\n",procname);
	return(PAM_AUTH_ERR);
    };
    buf[buflen]='\0';
    snprintf(buf,buflen,"GARBLED_PASSWORD=%s",local_password);
    retval = pam_putenv(pamh, buf);
    if(retval != PAM_SUCCESS){
	syslog(LOG_CRIT, "%s: pam_putenv failed: %s\n",procname, 
	       pam_strerror(pamh,retval));
	return(PAM_AUTH_ERR);
    }
    /* no errors */
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s: succeeded",procname);
#   endif    
    memset(buf,'\0',buflen);
    free(buf);
    memset(local_password,'\0',strlen(local_password));
    free(local_password);

    return(PAM_SUCCESS);
}

PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
			      int argc, const char **argv)
{
    const char *procname="pam_sm_setcred";

    int	i, retval=0, ignore_root=0,is_root=0;
    char *username=NULL, *password=NULL, *password_ptr=NULL, *buf=NULL;
    
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s",procname);
#   endif   

    /* get user name */
    retval = pam_get_user(pamh, (void *) &username, NULL);
    if (retval != PAM_SUCCESS) return(retval);

    /* is root logging in? */
    if (strcmp(username, "root") == 0) is_root=1;

 
    /* handle arguments */
    for (i=0; i<argc; i++)
	{
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: Argument #%d: '%s'", procname, i, argv[i]);
#           endif    
	    
	    if (strcmp(argv[i], "uniklu_helper_program") == 0)
		{
		    /* next argument is uniklu_helper program name */
		    i++;
		    /* ignored */
		}
	    else if (strcmp(argv[i], "ignore_root") == 0)
		{
		    /* skip root logins */
                    ignore_root=1;
		}
	    else
		{
		    /* unknown option! write to syslog! */
		    syslog(LOG_ERR, "%s: Unknown option '%s'", procname,
			   argv[i]);
		}
	}
    
    if (is_root && ignore_root)
	{
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: ignore root login",procname);
#           endif    
	    return(PAM_SUCCESS);
	}
    /* get password from PAM environment */
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s: get password",procname);
#   endif    
    
    password_ptr=(char *)pam_getenv(pamh, "GARBLED_PASSWORD");
    if(password_ptr==NULL) {
	syslog(LOG_CRIT, "%s: pam_getenv for GARBLED_PASSWORD failed.\n",
	       procname);
	return(PAM_AUTH_ERR);
    };
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s: got garbled password: '%s', length %d",procname, 
	   PASSWD_PTR, strlen(PASSWD_PTR));
#   endif    


    /* make a local copy of password, which will be stored as PAM data later */
    if((password=(char*) malloc(sizeof(char)*(strlen(password_ptr)+1))) 
       == NULL) {
	syslog(LOG_CRIT, "%s: Out of memory!\n",procname);
	return(PAM_AUTH_ERR);
    };
    strncpy(password,(const char*)password_ptr,(size_t)strlen(password_ptr));
    password[strlen(password_ptr)]='\0';
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s: local copy of garbled password: '%s', length %d",procname,
	   PASSWD, strlen(PASSWD));
#   endif    
    /* delete GARBLED_PASSWORD from PAM enviroment */
    if((buf=(char*) malloc(sizeof(char)*(strlen(password)+20))) ==NULL) {
	syslog(LOG_CRIT, "%s: Out of memory!\n",procname);
	return(PAM_AUTH_ERR);
    };
    snprintf(buf,20,"GARBLED_PASSWORD=''");
    retval = pam_putenv(pamh, buf);
    if(retval != PAM_SUCCESS){
	syslog(LOG_CRIT, "%s: pam_putenv failed: %s\n",procname, 
	       pam_strerror(pamh,retval));
	return(PAM_AUTH_ERR);
    }
    memset(buf,'\0',20);
    free(buf);
 
    /* check if environment is now clean: 
       password_ptr=(char *)pam_getenv(pamh, "GARBLED_PASSWORD");
       if(password_ptr==NULL) {
       syslog(LOG_CRIT, "%s: pam_getenv failed\n",procname);
       return(PAM_AUTH_ERR);
       };
       syslog(LOG_DEBUG, "%s: still got password !!: '%s', length %d",
              procname, password_ptr, strlen(password_ptr));
    */

    /* store garbled password as PAM data (invisible to applications) */
    retval = pam_set_data(pamh, UNIQUE_DATA_NAME, password,
			  pam_linux_uniklu_cleanup_password);
    if(retval != PAM_SUCCESS){
	syslog(LOG_CRIT, "%s: pam_set_data failed: %s\n",procname, 
	       pam_strerror(pamh,retval));
	return(PAM_AUTH_ERR);
    }
    
    /* really nothing more to do */
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s: succeeded",procname);
#   endif    
    return(PAM_SUCCESS);
}
