/*
 * PAM module for kerberized coda only.
 * This module should be used as "optional" in "session", NOT
 * for authentication.
 * ver 0.1
 * arguments:
 *  open_sesssion: ignore_root, clog /path/to/kclog/binary
 *  close_session: nocunlog, cunlog /path/to/cunlog/binary
 *
 * it runs kclog program at open_session() to aquire Coda tokens
 * and optionally cunlog at close_session() to destroy them.
 *
 * use kerberos pam module for authentication, then the things will work
 * transparently.
 *
 * Ivan Popov <pin@math.chalmers.se>, May 2001
 *
 * The module is an extensive fix/modification of
 *
 * Pam Coda Module
 * ALPHA - V0.2 
 * Robin.Gareus@urz.uni-heidelberg.de
 * http://www.rzuser.uni-heidelberg.de/~x42/coda/index.html
 *
 * This Software is distributed under the terms of the GNU 
 * General Public License (GPL)
 *
 * 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.
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <time.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/wait.h>
#include <sys/types.h>


#define CLOG_PROGRAM "/usr/local/bin/kclog"
#define CUNLOG_PROGRAM "/usr/local/bin/cunlog"

#include <security/pam_modules.h>
#include <security/_pam_macros.h>

PAM_EXTERN
int pam_sm_open_session(pam_handle_t * pamh, int flags, int argc
			,const char **argv)
{
    int retval;
    const char *user = NULL;
    struct passwd *pw = NULL;
    /* Flags for options */
    int ignore_root = 0;
    pid_t code;
    int status, number = 0, pipedes[2];
    char *cargv[16];
    char *clog_program;
    char **en;

    clog_program = malloc (strlen(CLOG_PROGRAM)*sizeof(char));
    strcpy (clog_program,CLOG_PROGRAM);

/* Parse parameters for module */
    for (; argc-- > 0; argv++) {
	if (strcmp(*argv, "ignore_root") == 0)
	    ignore_root = 1;
        else if (strcmp(*argv,"clog") == 0) {
            if (argc-- > 0) {
            clog_program = realloc (clog_program,strlen(*argv++)*sizeof(char));
            strcpy (clog_program,*argv);
            }
	}
    }

    retval = pam_get_user(pamh, &user, NULL);
    if (retval != PAM_SUCCESS)
        return PAM_SESSION_ERR;

    if (user == NULL || !isalnum(*user)) {
        if (user)
            syslog(LOG_ERR, "pam_kcoda: bad username [%s]", user);
        return PAM_SESSION_ERR;
    }

/* Get User Information */
    pw = getpwnam(user);

/* User does not exist */
    if (pw == NULL)
        return PAM_SESSION_ERR;

/* Handle Root */
    if (pw->pw_uid == 0 && ignore_root == 1)
	return PAM_SUCCESS;

/* set up pipe for communication with clog */
    if (pipe(pipedes) != 0) {
	syslog(LOG_CRIT, "pam_kcoda: Can't open pipe");
	return PAM_SESSION_ERR;
    }

/* set up arguments for clog */
    cargv[number++] = "clog";
    cargv[number++] = user;
    cargv[number] = NULL;

    code = fork();
    if (code == -1) {
	syslog(LOG_ERR, "pam_kcoda: fork faild");
	return PAM_SESSION_ERR;
    }
    if (code == 0) {
	if (setgid(pw->pw_gid) < 0) {
	    syslog(LOG_ERR, "pam_kcoda: setgid(%d) failed",
		   pw->pw_gid);
	    return PAM_SESSION_ERR;
	}
	if (setuid(pw->pw_uid) < 0) {
	    syslog(LOG_ERR, "pam_kcoda: setuid(%d) failed",
		   pw->pw_uid);
	    return PAM_SESSION_ERR;
	}
	close(0);		/* stdin */
	dup(pipedes[0]);
	close(pipedes[0]);
	close(1);		/* stdout */
	dup(pipedes[1]);
	close(pipedes[1]);
	close(2);
	dup(1);
        if( (en=pam_getenvlist(pamh)) != NULL )
          execve(clog_program, cargv, en);
        else
          execv(clog_program, cargv);
	/* close pipe */
	close(0);		/* stdin */
	close(1);		/* stdout */
	close(2);		/* stdout */
	/* Error: exec did return! */
	syslog(LOG_CRIT, "pam_kcoda: exec returned");
	exit( 1 );
    }
/* close pipe */
    close(pipedes[1]);
/* read what kclog tries to tells us :) -- pin */
    {char b[512]; read(pipedes[0],b,512);}
    close(pipedes[0]);
/* wait for child */
    while (code != wait(&status) && code != -1) ;
    if( code == -1 )
/* we did't get the child status (why?)
   or got interrupt - it might be ok but we are paranoid :-) */
	return PAM_SESSION_ERR;
    user = NULL;		/* clean up */
    if (WIFEXITED(status))
	if (WEXITSTATUS(status) == 0)
	    return PAM_SUCCESS;
    syslog(LOG_NOTICE, "pam_kcoda: pam_clog[%d]: unsuccessful",
	   getpid());
    return PAM_SESSION_ERR;
}

PAM_EXTERN
int pam_sm_close_session(pam_handle_t * pamh, int flags, int argc
			,const char **argv)
{
    int retval;
    const char *user = NULL;
    struct passwd *pw = NULL;
    pid_t code;
    int status, number = 0, pipedes[2];
    char *cargv[16];
    char *cunlog_program;
    char **en;

    cunlog_program = malloc (strlen(CUNLOG_PROGRAM)*sizeof(char));
    strcpy (cunlog_program,CUNLOG_PROGRAM);

/* Parse parameters for module */
    for (; argc-- > 0; argv++) {
        if (strcmp(*argv,"nocunlog") == 0)
            return PAM_SUCCESS;
        else if (strcmp(*argv,"cunlog") == 0) {
            if (argc-- > 0) {
            cunlog_program = realloc (cunlog_program,strlen(*argv++)*sizeof(char));
            strcpy (cunlog_program,*argv);
            }
        }
    }

    retval = pam_get_user(pamh, &user, NULL);
    if (retval != PAM_SUCCESS)
        return PAM_SESSION_ERR;

    if (user == NULL || !isalnum(*user)) {
        if (user)
            syslog(LOG_ERR, "pam_kcoda: bad username [%s]", user);
        return PAM_SESSION_ERR;
    }

/* Get User Information */
    pw = getpwnam(user);

/* User does not exist */
    if (pw == NULL)
        return PAM_SESSION_ERR;

/* set up arguments for cunlog */
    cargv[number++] = "cunlog";
    cargv[number] = NULL;

    code = fork();
    if (code == -1) {
	syslog(LOG_ERR, "pam_kcoda: fork faild");
	return PAM_SESSION_ERR;
    }
    if (code == 0) {
	if (setgid(pw->pw_gid) < 0) {
	    syslog(LOG_ERR, "pam_kcoda: setgid(%d) failed",
		   pw->pw_gid);
	    return PAM_SESSION_ERR;
	}
	if (setuid(pw->pw_uid) < 0) {
	    syslog(LOG_ERR, "pam_kcoda: setuid(%d) failed",
		   pw->pw_uid);
	    return PAM_SESSION_ERR;
	}
	close(0);		/* stdin */
	dup(pipedes[0]);
	close(pipedes[0]);
	close(1);		/* stdout */
	dup(pipedes[1]);
	close(pipedes[1]);
	close(2);
	dup(1);
        if( (en=pam_getenvlist(pamh)) != NULL )
          execve(cunlog_program, cargv, en);
        else
          execv(cunlog_program, cargv);
	/* close pipe */
	close(0);		/* stdin */
	close(1);		/* stdout */
	close(2);		/* stdout */
	/* Error: exec did return! */
	syslog(LOG_CRIT, "pam_kcoda: exec returned");
	exit( 1 );
    }
/* close pipe */
    close(pipedes[1]);
/* read what cunlog tries to tells us :) -- pin */
    {char b[512]; read(pipedes[0],b,512);}
    close(pipedes[0]);
/* wait for child */
    while (code != wait(&status) && code != -1) ;
    if( code == -1 )
/* we did't get the child status (why?)
   or got interrupt - it might be ok but we are paranoid :-) */
        return PAM_SESSION_ERR;
    user = NULL;                /* clean up */
    if (WIFEXITED(status))
	if (WEXITSTATUS(status) == 0)
	    return PAM_SUCCESS;
    syslog(LOG_NOTICE, "pam_kcoda: pam_cunlog[%d]: unsuccessful",
	   getpid());
    return PAM_SESSION_ERR;
}

/* end of module definition */
