/* 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_helper.c,v 1.9 2009-01-11 15:38:07 agebhard Exp $
 * 
 * Last Revised on
 * $Date: 2009-01-11 15:38:07 $
 * 
 * Changelog:
 *
 * $Log: pam_linux_uniklu_helper.c,v $
 * Revision 1.9  2009-01-11 15:38:07  agebhard
 * reduce compiler warnings
 *
 * Revision 1.8  2009-01-09 10:17:09  agebhard
 * ...
 *
 * Revision 1.7  2004/10/28 21:40:47  agebhard
 * working again (debian sarge)
 *
 * Revision 1.6  2004/10/28 18:31:43  agebhard
 * rename mount-home program to uniklu_helper
 *
 * Revision 1.5  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.4  2002/06/17 09:57:28  agebhard
 * more cvs keywords ...
 *
 * 
 */

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

int pam_linux_uniklu_helper(char *username, char *password,
			        const char *uniklu_helper_program)
{
    const char *procname="pam_linux_uniklu_helper";
    pid_t pid;
    int	status, number=0, pipedes[2];
    char *argv[64];
    struct passwd *pwent;
    uid_t my_uid;
    gid_t my_gid;

#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s", procname);
#   endif
    /* sanity check: don't allow an empty password */
    if ((int) strlen(password) == 0) 
	{
	    syslog(LOG_ERR, "%s: Empty password is not allowed!\n", procname);
	    return(PAM_AUTH_ERR);
	}
    /* check for username */
    if ((int) strlen(username) == 0) 
	{
	    syslog(LOG_ERR, "%s: Empty username is not allowed!\n", procname);
	    return(PAM_AUTH_ERR);
	}
    /* check if uniklu_helper_program exists */
    if (access(uniklu_helper_program, X_OK) != 0)
	{
	    syslog(LOG_CRIT, "%s: uniklu_helper program '%s' not executable",
		   procname, uniklu_helper_program);
	    return(PAM_AUTHINFO_UNAVAIL);
	}

    /* get UID for username */
    pwent = getpwnam(username);
    /* set up arguments for uniklu_helper */
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s: prepare uniklu_helper call for user %s, password %s",
	   procname, username, PASSWD);
#   endif
    argv[number++] = (char *) uniklu_helper_program;
    argv[number++] = "-U";
    argv[number++] = username;
    argv[number] = NULL;
    /* set up pipe for communication with uniklu_helper */
#   ifdef DEBUG
    syslog(LOG_DEBUG, "%s: setup pipes",procname);
#   endif
    if (pipe(pipedes) != 0)
	{
	    syslog(LOG_CRIT, "%s: Can't open pipe",procname);
	    return(PAM_AUTHINFO_UNAVAIL);
	}
    
    /* fork "uniklu_helper" */
    pid = fork();
    switch (pid)
	{
	case (-1):
	    /* Error: fork failed */
	    syslog(LOG_CRIT, "%s[%d]: fork failed",
		   procname,getpid());
	    return(PAM_AUTHINFO_UNAVAIL);
	case (0):
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: forked, child",procname);
#           endif
	    /* set up file descriptors for pipe communications */
	    close(0); /* stdin */
	    dup(pipedes[0]);
	    close(pipedes[0]);
	    close(1); /* stdout */
	    dup(pipedes[1]);
	    close(pipedes[1]);
	    /* exec uniklu_helper (with normal lifetime) */
	    my_uid=getuid();
	    my_gid=getgid();
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: setuid/setgid to %i/%i", procname, 
                   pwent->pw_uid, pwent->pw_gid);
#           endif
	    setuid(pwent->pw_uid);
	    setgid(pwent->pw_gid);
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: execv %s",procname, uniklu_helper_program);
#           endif
	    execv(uniklu_helper_program, argv);
	    setuid(my_uid);
	    setgid(my_gid);
	    memset(password, '\0', strlen(password));
	    /* close pipe */
	    close(0); /* stdin */
	    close(1); /* stdout */
	    /* Error: exec did return! */
	    syslog(LOG_CRIT, "%s[%d]: exec returned",
		   procname,getpid());
	    return(PAM_AUTHINFO_UNAVAIL);
	default:
#           ifdef DEBUG
	    syslog(LOG_DEBUG, "%s: forked, parent",procname);
#           endif
	    /* write password */
	    write(pipedes[1], password, strlen(password));
	    write(pipedes[1], "\n", 1);
	    /* close pipe */
	    close(pipedes[0]);
	    close(pipedes[1]);
	    /* wait for child */
	    if (pid != wait(&status)) return(PAM_AUTHINFO_UNAVAIL);
	    if (WIFEXITED(status))
		if (WEXITSTATUS(status) == 0)
		    return(PAM_SUCCESS);
	    syslog(LOG_NOTICE, "%s[%d]: unsuccessful",
		   procname,getpid());
	}
    
    /* return error */
    return(PAM_AUTH_ERR);
}
