/* MODULE: auth_pam */

/* COPYRIGHT
 * Copyright (c) 2000 Fabian Knittel.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain any existing copyright
 *    notice, and this entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 2. Redistributions in binary form must reproduce all prior and current
 *    copyright notices, this list of conditions, and the following
 *    disclaimer in the documentation and/or other materials provided
 *    with the distribution.
 *
 * 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(S) 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.
 * END COPYRIGHT */

/*
 * Pluggable Authentication Modules, PAM(8), based authentication module
 * for saslauthd.
 *
 * Written by Fabian Knittel <fknittel@gmx.de>. Original implementation
 * Debian's pwcheck_pam daemon by Michael-John Turner <mj@debian.org>.
 */

/* PUBLIC DEPENDENCIES */
#include <config.h>
#include "mechanisms.h"

#ifdef AUTH_PAM

# include <string.h>
# include <syslog.h>
# include <security/pam_appl.h>

# include "auth_pam.h"
/* END PUBLIC DEPENDENCIES */


/* Structure for application specific data passed through PAM
 * to our conv call-back routine saslauthd_pam_conv. */
typedef struct {
    const char *login;			/* plaintext authenticator */
    const char *password;		/* plaintext password */
    pam_handle_t *pamh;			/* pointer to PAM handle */
} pam_appdata;

# define RETURN(x) return strdup(x)


/* FUNCTION: saslauthd_pam_conv */

/* SYNOPSIS
 * Call-back function used by the PAM library to communicate with us. Each
 * received message expects a response, pointed to by resp.
 * END SYNOPSIS */

static int				/* R: PAM return code */
saslauthd_pam_conv (
  /* PARAMETERS */
  int num_msg,				/* I: number of messages */
  const struct pam_message **msg,	/* I: pointer to array of messages */
  struct pam_response **resp,		/* O: pointer to pointer of response */
  void *appdata_ptr			/* I: pointer to app specific data */
  /* END PARAMETERS */
  )
{
    /* VARIABLES */
    pam_appdata *my_appdata;		/* application specific data */
    struct pam_response *my_resp;	/* response created by this func */
    int i;				/* loop counter */
    const char *login_prompt;		/* string prompting for user-name */
    int rc;				/* return code holder */
    /* END VARIABLES */

    my_appdata = appdata_ptr;

    my_resp = malloc(sizeof(struct pam_response) * num_msg);
    if (my_resp == NULL)
	return PAM_CONV_ERR;

    for (i = 0; i < num_msg; i++)
	switch (msg[i]->msg_style) {
	/*
	 * We assume PAM_PROMPT_ECHO_OFF to be a request for password.
	 * This assumption might be unsafe.
	 *
	 * For PAM_PROMPT_ECHO_ON we first check whether the provided
	 * request string matches PAM_USER_PROMPT and, only if they do
	 * match, assume it to be a request for the login.
	 */
	case PAM_PROMPT_ECHO_OFF:	/* password */
	    my_resp[i].resp = strdup(my_appdata->password);
	    if (my_resp[i].resp == NULL) {
		syslog(LOG_DEBUG, "DEBUG: saslauthd_pam_conv: strdup failed");
		goto ret_error;
	    }
	    my_resp[i].resp_retcode = PAM_SUCCESS;
	    break;

	case PAM_PROMPT_ECHO_ON:	/* username? */
	    /* Recheck setting each time, as it might have been changed
	       in the mean-while. */
	    rc = pam_get_item(my_appdata->pamh, PAM_USER_PROMPT,
			      (void *) &login_prompt);
	    if (rc != PAM_SUCCESS) {
		syslog(LOG_DEBUG, "DEBUG: saslauthd_pam_conv: unable to read "
		       "login prompt string: %s",
		       pam_strerror(my_appdata->pamh, rc));
		goto ret_error;
	    }

	    if (strcmp(msg[i]->msg, login_prompt) == 0) {
		my_resp[i].resp = strdup(my_appdata->login);
		my_resp[i].resp_retcode = PAM_SUCCESS;
	    } else {			/* ignore */
		syslog(LOG_DEBUG, "DEBUG: saslauthd_pam_conv: unknown prompt "
		       "string: %s", msg[i]->msg);
		my_resp[i].resp = NULL;
		my_resp[i].resp_retcode = PAM_SUCCESS;
	    }
	    break;

	case PAM_ERROR_MSG:		/* ignore */
	case PAM_TEXT_INFO:		/* ignore */
	    my_resp[i].resp = NULL;
	    my_resp[i].resp_retcode = PAM_SUCCESS;
	    break;

	default:			/* error */
	    goto ret_error;
	}
    *resp = my_resp;
    return PAM_SUCCESS;

ret_error:
    /*
     * Free response structure. Don't free my_resp[i], as that
     * isn't initialised yet.
     */
    {
	int y;

	for (y = 0; y < i; y++)
	    if (my_resp[y].resp != NULL)
		free(my_resp[y].resp);
	free(my_resp);
    }
    return PAM_CONV_ERR;
}

/* END FUNCTION: saslauthd_pam_conv */

/* FUNCTION: auth_pam */

char *					/* R: allocated response string */
auth_pam (
  /* PARAMETERS */
  const char *login,			/* I: plaintext authenticator */
  const char *password			/* I: plaintext password */
  /* END PARAMETERS */
  )
{
    /* VARIABLES */
    pam_appdata my_appdata;		/* application specific data */
    struct pam_conv my_conv;		/* pam conversion data */
    pam_handle_t *pamh;			/* pointer to PAM handle */
    int rc;				/* return code holder */
    /* END VARIABLES */

    my_appdata.login = login;
    my_appdata.password = password;
    my_appdata.pamh = NULL;

    my_conv.conv = saslauthd_pam_conv;
    my_conv.appdata_ptr = &my_appdata;

    rc = pam_start("cyrus", login, &my_conv, &pamh);
    if (rc != PAM_SUCCESS) {
	syslog(LOG_DEBUG, "DEBUG: auth_pam: pam_start failed: %s",
	       pam_strerror(pamh, rc));
	RETURN("NO PAM start error");
    }

    my_appdata.pamh = pamh;

    rc = pam_authenticate(pamh, PAM_SILENT);
    if (rc != PAM_SUCCESS) {
	syslog(LOG_DEBUG, "DEBUG: auth_pam: pam_authenticate failed: %s",
	       pam_strerror(pamh, rc));
	pam_end(pamh, rc);
	RETURN("NO PAM auth error");
    }

    rc = pam_acct_mgmt(pamh, PAM_SILENT);
    if (rc != PAM_SUCCESS) {
	syslog(LOG_DEBUG, "DEBUG: auth_pam: pam_acct_mgmt failed: %s",
	       pam_strerror(pamh, rc));
	pam_end(pamh, rc);
	RETURN("NO PAM acct error");
    }

    pam_end(pamh, PAM_SUCCESS);
    RETURN("OK");
}

/* END FUNCTION: auth_pam */

#else /* !AUTH_PAM */

char *
auth_pam(
  const char *login __attribute__((unused)),
  const char *password __attribute__((unused))
  )
{
    return NULL;
}

#endif /* !AUTH_PAM */

/* END MODULE: auth_pam */
