/*
 * $Id: setcred.c,v 1.11 1999/11/10 13:19:45 agalat Rel $
 */

/*
 * Here we set the user's groups and return their uid
 */

#include <sys/types.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <grp.h>

#include "../../common/include/config.h"
#ifdef HAVE_PWDB
#include <pwdb/pwdb_public.h>
#endif

#include <security/pam_appl.h>
#include <security/pam_misc.h>

#include "../../common/include/config.h"

#include "../include/setcred.h"

int set_user_credentials(pam_handle_t *pamh, int login, 
				const char **user, uid_t *uid, 
				const char **shell)
{
    const struct passwd *pw;
    int retval;

    /*
     * Identify the user from PAM.
     */

    D(("get user from pam"));
    retval = pam_get_item(pamh, PAM_USER, (const void **)user);
    if (retval != PAM_SUCCESS || *user == NULL || **user == '\0') {
	D(("error identifying user from PAM."));
	return retval;
    }

    /*
     * identify user, update their name too. This is most likely only
     * useful with libpwdb where guest may be mapped to guest37 ...
     */

    pw = getpwnam(*user);
    if (pw == NULL || (*user = x_strdup(pw->pw_name)) == NULL) {
	D(("failed to identify user"));
	return PAM_USER_UNKNOWN;
    }

    *uid = pw->pw_uid;

    *shell = x_strdup(pw->pw_shell);
    if (*shell == NULL) {
	return PAM_CRED_ERR;
    }

    /* initialize groups */

#ifdef HAVE_PWDB
{
    const struct pwdb *p=NULL;
    const struct pwdb_entry *pwe_gid=NULL;
    const struct pwdb_entry *pwe_groupids=NULL;
    gid_t *gids=NULL;
    int gsize=0;

    retval = pwdb_locate("user", PWDB_DEFAULT, *user, PWDB_ID_UNKNOWN, &p);
    if (retval != PWDB_SUCCESS) {
	D(("couldn't identify user; %s", pwdb_strerror(retval)));
	return PAM_USER_UNKNOWN;
    }

    /*
     * Here, we set the groups that user is a member of...
     *
     * These groups are:
     *    1. gid;         - user's primary group
     *    2. groupids;    - groups listed in their pwdb files
     */

    retval = pwdb_get_entry(p, "gid", &pwe_gid);
    if (retval != PWDB_SUCCESS) {
	D(("failed to identify user's group; %s", user));
	return PAM_CRED_ERR;
    }
    
    /* now try to get the groupids (use pw for reference) */

    (void) pwdb_request("group", PWDB_DEFAULT, "groupids", &p);
    retval = pwdb_get_entry(p, "groupids", &pwe_groupids);
    if (retval == PWDB_SUCCESS) {
	/*
	 * there are some supplementary groups to add
	 */
	gids = malloc(gsize = pwe_groupids->length);
	if (gids == NULL) {
	    D(("Failed to find user's groups; %s", pam_strerror(pamh,retval)));
	    return PAM_CRED_ERR;
	}
	memcpy(gids, pwe_groupids->value, pwe_groupids->length);
    }
    
    /*
     * here pwe_gid and gids contain all of the groups we are giving to
     * the user
     */

    if (setgid(* (gid_t *) pwe_gid->value) != 0
	|| (gids && setgroups(gsize/sizeof(gid_t), gids) != 0)) {
	D(("failed to set user's groups"));
	return PAM_PERM_DENIED;
    }

    memset(gids,0,gsize);                                       /* clean up */
    _pam_drop(gids);
}
#else /* Do not HAVE_PWDB - so use the Sys V/libc way (initgroups) */
    {
	if (initgroups(pw->pw_name, pw->pw_gid) != 0
	    || setgid(pw->pw_gid) != 0) {
	    return PAM_PERM_DENIED;
	}
    }
#endif /* HAVE_PWDB */

    /*
     * Add the LOGNAME and HOME environment variables.
     */

    D(("add some variables"));
    if (login) {
	/* set LOGNAME, HOME */
	if (pam_misc_setenv(pamh, "LOGNAME", *user, 0) != PAM_SUCCESS) {
	    D(("failed to set LOGNAME"));
	    return PAM_CRED_ERR;
	}
	if (pam_misc_setenv(pamh, "HOME", pw->pw_dir, 0) != PAM_SUCCESS) {
	    D(("failed to set HOME"));
	    return PAM_CRED_ERR;
	}
    }

    pw = NULL;                                                  /* be tidy */

    /*
     * next, we call the PAM framework to add/enhance the credentials
     * of this user [it may change the user's home directory...]
     */

    retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
    if (retval != PAM_SUCCESS) {
	D(("failed to set PAM credentials; %s", pam_strerror(pamh,retval)));
	return retval;
    }

    return PAM_SUCCESS;
}

