/* Copyright (C) 1999 Beau Kuiper

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "ftpd.h"
#include "auth.h"

#ifdef HAVE_PAM_START

/* Are we debugging pam at the moment? */
#define PAMDEBUG

#ifdef PAMDEBUG
#define PAMLOG(handle, name, user, result) log_giveentry(MYLOG_INFO, NULL, safe_snprintf(name" on %s returned %d. (%s)", user, result, pam_strerror(handle, result))) 
#else
#define PAMLOG(handle, name, user, result) NULL
#endif

#include <security/pam_appl.h>

/* This file contains code to autheticate using PAM password. It was written
   using proftpd as a reference */

typedef struct 
{
	char *password;
	char *username;
	char *homedir;
	char *passwduser;
	int uid;
	int gid;
	pam_handle_t *pamh;
	int retval;
} PASSWDSTRUCT;
 
PASSWDSTRUCT *currenthandle = NULL;

static int pamauth_conv(int num_msg, struct pam_message **msg,
			struct pam_response **resp,
			void *appdata_ptr)
{
	struct pam_response *response = NULL;
 	int count;

 	/* I cannot use mallocwrapper here because it will break counters
 	   since PAM takes the memory */
 	response = malloc(sizeof(struct pam_response) * num_msg);
 	
 	if (response == NULL)
 		return(PAM_CONV_ERR);
 	
 	for (count = 0; count < num_msg; count++)
 	{
 		response[count].resp_retcode = PAM_SUCCESS;
 		switch(msg[count]->msg_style)
 		{
 			case PAM_PROMPT_ECHO_ON:
 				if (currenthandle == NULL)
 					ERRORMSGFATAL("PAM is being mean! It wants a username I don't know");
 				response[count].resp = strdup(currenthandle->username);
 				if (!response[count].resp)
 					return(PAM_CONV_ERR);
 				break;
 			case PAM_PROMPT_ECHO_OFF:
				if (currenthandle == NULL)
 					ERRORMSGFATAL("PAM is being mean! It wants a password I don't know");
  				response[count].resp = currenthandle->password;
 				break; 
 			case PAM_TEXT_INFO: 
 			case PAM_ERROR_MSG:
 				response[count].resp = NULL;
 				break;
 			
 			default:	/* yuck, an error! */
 				free(response);	
				return(PAM_CONV_ERR);
		}
	}
	
	*resp = response;
	return(PAM_SUCCESS);
}	

static struct pam_conv mypam = {
  &pamauth_conv,
  NULL
};

void pamauth_freehandle(void *h)
{
	PASSWDSTRUCT *handle = (PASSWDSTRUCT *)h;
	pam_end(handle->pamh, handle->retval);

	freeifnotnull(handle->homedir);
	freewrapper(handle->passwduser);
	freewrapper(handle->username);
	freewrapper(handle);
}
 	
void *pamauth_gethandle(FTPSTATE *peer, TOKENSET *tset, char *username, int *err)
{
	PASSWDSTRUCT *newhandle;
	char *pamservice = NULL;
	char *pamuser = NULL;
	struct passwd *passent;
	
	pamservice = mktokconfstr(tset, auth_getcursectionid(peer), "pam_service", "ftp");
	pamuser = mktokconfstr(tset, auth_getcursectionid(peer), "pam_user", "%U");
	
	newhandle = mallocwrapper(sizeof(PASSWDSTRUCT));
	newhandle->username = strdupwrapper(username);
	newhandle->passwduser = pamuser;
	newhandle->homedir = NULL;

	/* if we can't get the username from the password file, assume it
	   doesn't exist */

	*err = AUTH_USERNKNOW;
	passent = getpwnam(pamuser);
	if (passent == NULL)
	{
		if (strcmp(username, pamuser) == 0)
			log_giveentry(MYLOG_INFO, NULL, safe_snprintf("Username '%s' does not exist for pam authentication", pamuser));
		goto error;
	}
	newhandle->homedir = strdupwrapper(passent->pw_dir);
	newhandle->uid = passent->pw_uid;
	newhandle->gid = passent->pw_gid;
	newhandle->pamh = NULL;
	newhandle->retval = pam_start(pamservice, username, &mypam, &(newhandle->pamh));

	/* lets log all stuff that pam does! */
	PAMLOG(newhandle->pamh, "pam_start", username, newhandle->retval);
	
	/* if above didn't work, it is a nasty error and should cancel
	   authentication */
	*err = AUTH_ERROR;
	if (newhandle->retval != PAM_SUCCESS)
		goto error;
	
	*err = AUTH_OK;
	freewrapper(pamservice);
	return(newhandle);

error:
	pamauth_freehandle(newhandle);
	freewrapper(pamservice);
	return(NULL);
}

int pamauth_checkpasswd(void *h, char *password, char **errmsg)
{
	PASSWDSTRUCT *handle = (PASSWDSTRUCT *)h;
	
	currenthandle = handle;
	handle->password = strdup(password);
	if (!handle->password)
		ERRORMSGFATAL("out of memory in PAM module!");
	
	/* This will authenticate the password */
	/* I think it uses all the auth PAM configuration lines */
	handle->retval = pam_authenticate(handle->pamh, PAM_SILENT);
	PAMLOG(handle->pamh, "pam_authenticate", handle->username, handle->retval);
	
	/* This part now checks the users account */
	if (handle->retval == PAM_SUCCESS)
		handle->retval = pam_acct_mgmt(handle->pamh, 0);
	else
		return(FALSE);
	
	PAMLOG(handle->pamh, "pam_acct_mgmt", handle->username, handle->retval);
	
	if (handle->retval == PAM_SUCCESS)
		handle->retval = pam_setcred(handle->pamh, PAM_ESTABLISH_CRED);
	else
	{
		*errmsg = safe_snprintf("Account Problem");
		return(FALSE);
	}

	PAMLOG(handle->pamh, "pam_setcred", handle->username, handle->retval);
	if (handle->retval == PAM_SUCCESS)
		return(TRUE);

	*errmsg = safe_snprintf("Credantial Problem");
	return(FALSE);
}

char *pamauth_gethomedir(void *h)
{
	return(((PASSWDSTRUCT *)h)->homedir);
}

char *pamauth_getrootdir(void *h)
{
	return("/");
}

uid_t pamauth_getuseruid(void *h)
{
	return(((PASSWDSTRUCT *)h)->uid);
}

gid_t pamauth_getusergid(void *h)
{
	return(((PASSWDSTRUCT *)h)->gid);
}

gid_t *pamauth_getusersupgid(void *h)
{
	return(getusergrouplist(((PASSWDSTRUCT *)h)->passwduser));
}

PERMSTRUCT pamauth_commands =
{
	pamauth_checkpasswd,
	pamauth_gethomedir,
	pamauth_getrootdir,
	pamauth_getuseruid,
	pamauth_getusergid,
	pamauth_getusersupgid,
	pamauth_gethandle,
	pamauth_freehandle,
	NULL,
};

#endif
