static const char rcsid[] = "$Id: pam.c,v 1.6 2004/04/30 17:00:58 will Exp $";
/* The code should compile with either ANSI C or K&R compilers. */

/*
 *      Copyright (c) 1993 by California Institute of Technology.
 *      Written by William Deich.  Not derived from licensed software.

 *    You may distribute under the terms of either the GNU General Public
 *    License or the Artistic License, as specified in the README file.
 */

#include "super.h"
#include "version.h"


#ifndef HAVE_PAM_START
int
get_pam(cmd, caller, user)
char *cmd;
char *caller;	/* the person who invoked super */
char *user;	/* the person whose authentication is required */
{
    Error(0, 1,
	"Error in super.tab file -- this copy of super was compiled on a host without support for authtype=pam\n");
}

#else

#ifndef HAVE_PAM_STRERROR
/* pam_strerror converts a PAM status code to a string, and a returns
 * a ptr to a static string.  This is the Linux pam_strerror() code,
 * except that I've #ifdef'd everything so that it compiles on systems
 * that don't have all of the PAM_xxx values.  (Note that this only
 * works if #define is used; an enum would break this.)
 */
char *
pam_strerror(pam_handle_t *pamh, int status)
{
    switch (errnum) {
    #ifdef PAM_SUCCESS
    case PAM_SUCCESS:
        return "Success";
    #endif
    #ifdef PAM_ABORT
    case PAM_ABORT: return "Critical error - immediate abort";
    #endif
    #ifdef PAM_OPEN_ERR
    case PAM_OPEN_ERR: return "dlopen() failure";
    #endif
    #ifdef PAM_SYMBOL_ERR
    case PAM_SYMBOL_ERR: return "Symbol not found";
    #endif
    #ifdef PAM_SERVICE_ERR
    case PAM_SERVICE_ERR: return "Error in service module";
    #endif
    #ifdef PAM_SYSTEM_ERR
    case PAM_SYSTEM_ERR: return "System error";
    #endif
    #ifdef PAM_BUF_ERR
    case PAM_BUF_ERR: return "Memory buffer error";
    #endif
    #ifdef PAM_PERM_DENIED
    case PAM_PERM_DENIED: return "Permission denied";
    #endif
    #ifdef PAM_AUTH_ERR
    case PAM_AUTH_ERR: return "Authentication failure";
    #endif
    #ifdef PAM_CRED_INSUFFICIENT
    case PAM_CRED_INSUFFICIENT:
        return "Insufficient credentials to access authentication data";
    #endif
    #ifdef PAM_AUTHINFO_UNAVAIL
    case PAM_AUTHINFO_UNAVAIL:
        return "Authentication service cannot retrieve authentication info.";
    #endif
    #ifdef PAM_USER_UNKNOWN
    case PAM_USER_UNKNOWN:
        return "User not known to the underlying authentication module";
    #endif
    #ifdef PAM_MAXTRIES
    case PAM_MAXTRIES:
        return "Have exhasted maximum number of retries for service.";
    #endif
    #ifdef PAM_NEW_AUTHTOK_REQD
    case PAM_NEW_AUTHTOK_REQD:
        return "Authentication token is no longer valid; new one required.";
    #endif
    #ifdef PAM_ACCT_EXPIRED
    case PAM_ACCT_EXPIRED: return "User account has expired";
    #endif
    #ifdef PAM_SESSION_ERR
    case PAM_SESSION_ERR:
        return "Cannot make/remove an entry for the specified session";
    #endif
    #ifdef PAM_CRED_UNAVAIL
    case PAM_CRED_UNAVAIL:
        return "Authentication service cannot retrieve user credentials";
    #endif
    #ifdef PAM_CRED_EXPIRED
    case PAM_CRED_EXPIRED: return "User credentials expired";
    #endif
    #ifdef PAM_CRED_ERR
    case PAM_CRED_ERR: return "Failure setting user credentials";
    #endif
    #ifdef PAM_NO_MODULE_DATA
    case PAM_NO_MODULE_DATA: return "No module specific data is present";
    #endif
    #ifdef PAM_BAD_ITEM
    case PAM_BAD_ITEM: return "Bad item passed to pam_*_item()";
    #endif
    #ifdef PAM_CONV_ERR
    case PAM_CONV_ERR: return "Conversation error";
    #endif
    #ifdef PAM_AUTHTOK_ERR
    case PAM_AUTHTOK_ERR: return "Authentication token manipulation error";
    #endif
    #ifdef PAM_AUTHTOK_LOCK_BUSY
    case PAM_AUTHTOK_LOCK_BUSY: return "Authentication token lock busy";
    #endif
    #ifdef PAM_AUTHTOK_DISABLE_AGING
    case PAM_AUTHTOK_DISABLE_AGING:
        return "Authentication token aging disabled";
    #endif
    #ifdef PAM_TRY_AGAIN
    case PAM_TRY_AGAIN: return "Failed preliminary check by password service";
    #endif
    #ifdef PAM_IGNORE
    case PAM_IGNORE: return "Please ignore underlying account module";
    #endif
    #ifdef PAM_MODULE_UNKNOWN
    case PAM_MODULE_UNKNOWN: return "Module is unknown";
    #endif
    #ifdef PAM_AUTHTOK_EXPIRED
    case PAM_AUTHTOK_EXPIRED: return "Authentication token expired";
    #endif
    #ifdef PAM_CONV_AGAIN
    case PAM_CONV_AGAIN: return "Conversation is waiting for event";
    #endif
    #ifdef PAM_INCOMPLETE
    case PAM_INCOMPLETE: return "Application needs to call libpam again";
    #endif
    }
    return "Unknown PAM error (need to upgrade libpam?)";
}
#endif	/* !HAVE_PAM_STRERROR */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Get authentication via PAM */
/* Return 1 on success, -1 on error. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
get_pam(cmd, caller, user)
char *cmd;
char *caller;	/* the person who invoked super */
char *user;	/* the person whose authentication is required */
{
    int misc_conv(int num_msg, const struct pam_message **msgm,
	      struct pam_response **response, void *appdata_ptr);

    static struct pam_conv conv = {
	misc_conv,
	NULL
    };
    pam_handle_t *pamh = NULL;
    int ntry, pamstatus;
    char msg[500];
    FILE *prompt_fp = NULL;
    char *prompt_dev="/dev/tty";

    if (pam_start(ONETRUENAME, user, &conv, &pamh) != PAM_SUCCESS) {
	return Error(0, 0, "Internal error: can't start PAM authentication!\n");
    }

    if (!(prompt_fp = fopen(prompt_dev, "r+"))) {
	return Error(0, 0,
		"Can't open %s for authentication message!\n", prompt_dev);
    }

    /* Note that the actual number of tries is the lesser of our MAXTRY 
     * and the PAM module's maximum...
     */
    for (ntry=0, pamstatus = PAM_AUTH_ERR;
			ntry < MAXTRY && pamstatus == PAM_AUTH_ERR; ntry++) {
	if (ntry == 0) {
	    if (localinfo.authinfo.prompt && localinfo.authinfo.prompt[0]) {
		stringcopy(msg,
			do_variables(localinfo.authinfo.prompt), sizeof(msg));
	    } else if (strcmp(caller, user) == 0) {
		(void) sprintf(msg,
		"Authentication is required for super command `%.400s'...",
		    cmd);
	    } else {
		(void) sprintf(msg,
	"Authentication for user %s is required for super command `%.400s'...",
		user, cmd);
	    }
	    fprintf(prompt_fp, "%s\n", msg);
	} else {
	    fprintf(prompt_fp, "Authentication failed; try again...\n");
	}
	pamstatus = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK);
    }
    fclose(prompt_fp);

    if (pamstatus != PAM_SUCCESS) {
	pam_end(pamh, pamstatus);
	return Error(0, 0, "Authentication failed: %s\n",
				    pam_strerror(pamh, pamstatus));
    }

    if ((pamstatus=pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
	pam_end(pamh, pamstatus);
	return Error(0, 0, "Authentication succeeded, \
but PAM doesn't grant access:\n\t%s\n", pam_strerror(pamh, pamstatus));
    }

    if (pam_end(pamh, pamstatus) != PAM_SUCCESS) {
	return Error(0, 0, "Internal error: failed to release authenticator\n");
    }
    return 1;
}
#endif /* HAVE_PAM_START */
