/* 
   Unix SMB/Netbios implementation.
   Version 2.0

   Winbind daemon - user related function

   Copyright (C) Tim Potter 2000
   
   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 of the License, 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 "winbindd.h"

/* Fill a pwent structure with information we have obtained */

static void winbindd_fill_pwent(struct winbindd_pw *pw, char *name,
                                uid_t unix_uid, gid_t unix_gid, 
                                char *full_name)
{
    pstring homedir;
    fstring name_domain, name_user;

    if (!pw || !name) {
        return;
    }

    /* Fill in uid/gid */

    pw->pw_uid = unix_uid;
    pw->pw_gid = unix_gid;

    /* Username */

    safe_strcpy(pw->pw_name, name, sizeof(pw->pw_name) - 1);

    /* Full name (gecos) */

    safe_strcpy(pw->pw_gecos, full_name, sizeof(pw->pw_gecos) - 1);

    /* Home directory and shell - use template config parameters.  The
       defaults are /tmp for the home directory and /bin/false for shell. */

    parse_domain_user(name, name_domain, name_user);

    pstrcpy(homedir, lp_template_homedir());

    pstring_sub(homedir, "%U", name_user);
    pstring_sub(homedir, "%D", name_domain);

    safe_strcpy(pw->pw_dir, homedir, sizeof(pw->pw_dir) - 1);

    safe_strcpy(pw->pw_shell, lp_template_shell(), sizeof(pw->pw_shell) - 1);

    /* Password - set to "x" as we can't generate anything useful here.
       Authentication can be done using the pam_ntdom module. */

    safe_strcpy(pw->pw_passwd, "x", sizeof(pw->pw_passwd) - 1);
}

/* Return a password structure from a username.  Specify whether cached data 
   can be returned. */

enum winbindd_result winbindd_getpwnam_from_user(struct winbindd_cli_state *state) 
{
    uint32 name_type, user_rid, group_rid;
    SAM_USERINFO_CTR user_info;
    DOM_SID user_sid;
    fstring name_domain, name_user, name, gecos_name;
    struct winbindd_domain *domain;
    uid_t uid;
    gid_t gid;

    /* Parse domain and username */
    parse_domain_user(state->request.data.username, name_domain, name_user);

    /* Reject names that don't have a domain - i.e name_domain contains the
       entire name. */
 
    if (strequal(name_domain, "")) {
        return WINBINDD_ERROR;
    }

    /* Get info for the domain */

    if ((domain = find_domain_from_name(name_domain)) == NULL) {
        DEBUG(0, ("could not find domain entry for domain %s\n", name_domain));
        return WINBINDD_ERROR;
    }

    /* Check for cached user entry */

    if (winbindd_fetch_user_cache_entry(name_domain, name_user,
					&state->response.data.pw)) {
            return WINBINDD_OK;
    }

    slprintf(name,sizeof(name),"%s\\%s", name_domain, name_user);

    /* Get rid and name type from name */
    /* the following costs 1 packet */
    if (!winbindd_lookup_sid_by_name(domain, name, &user_sid, &name_type)) {
        DEBUG(1, ("user '%s' does not exist\n", name_user));
        return WINBINDD_ERROR;
    }

    if (name_type != SID_NAME_USER) {
        DEBUG(1, ("name '%s' is not a user name: %d\n", name_user, name_type));
        return WINBINDD_ERROR;
    }

    /* Get some user info.  Split the user rid from the sid obtained from
       the winbind_lookup_by_name() call and use it in a
       winbind_lookup_userinfo() */
    
    sid_split_rid(&user_sid, &user_rid);

    /* the following costs 3 packets */
    if (!winbindd_lookup_userinfo(domain, user_rid, &user_info)) {
        DEBUG(1, ("pwnam_from_user(): error getting user info for user '%s'\n",
                  name_user));
        return WINBINDD_ERROR;
    }
    
    group_rid = user_info.info.id21->group_rid;
    unistr2_to_ascii(gecos_name, &user_info.info.id21->uni_full_name,
                     sizeof(gecos_name) - 1);

    free_samr_userinfo_ctr(&user_info);

    /* Resolve the uid number */

    if (!winbindd_idmap_get_uid_from_rid(domain->name, user_rid, &uid)) {
        DEBUG(1, ("error getting user id for user %s\n", name_user));
        return WINBINDD_ERROR;
    }

    /* Resolve the gid number */   

    if (!winbindd_idmap_get_gid_from_rid(domain->name, group_rid, &gid)) {
        DEBUG(1, ("error getting group id for user %s\n", name_user));
        return WINBINDD_ERROR;
    }

    /* Now take all this information and fill in a passwd structure */
            
    winbindd_fill_pwent(&state->response.data.pw, 
                        state->request.data.username, uid, gid, 
                        gecos_name);
            
    winbindd_fill_user_cache_entry(name_domain, name_user, 
                                   &state->response.data.pw);

    return WINBINDD_OK;
}       

/* Return a password structure given a uid number */

enum winbindd_result winbindd_getpwnam_from_uid(struct winbindd_cli_state 
                                                *state)
{
    DOM_SID user_sid;
    struct winbindd_domain *domain;
    uint32 user_rid, group_rid;
    fstring user_name, gecos_name;
    enum SID_NAME_USE name_type;
    SAM_USERINFO_CTR user_info;
    gid_t gid;

    /* Get rid from uid */
    if (!winbindd_idmap_get_rid_from_uid(state->request.data.uid, &user_rid,
                                         &domain)) {
        DEBUG(1, ("Could not convert uid %d to rid\n", 
                  state->request.data.uid));
        return WINBINDD_ERROR;
    }
    
    /* Check for cached uid entry */
    if (winbindd_fetch_uid_cache_entry(domain->name, state->request.data.uid,
				       &state->response.data.pw)) {
            return WINBINDD_OK;
    }


    /* Get name and name type from rid */

    sid_copy(&user_sid, &domain->sid);
    sid_append_rid(&user_sid, user_rid);

    if (!winbindd_lookup_name_by_sid(domain, &user_sid, user_name, 
                                     &name_type)) {
        fstring temp;

        sid_to_string(temp, &user_sid);
        DEBUG(1, ("Could not lookup sid %s\n", temp));
        return WINBINDD_ERROR;
    }

    if (strcmp("\\", lp_winbind_separator())) {
	    string_sub(user_name, "\\", lp_winbind_separator(), sizeof(fstring));
    }

    /* Get some user info */
    
    if (!winbindd_lookup_userinfo(domain, user_rid, &user_info)) {
        DEBUG(1, ("pwnam_from_uid(): error getting user info for user '%s'\n",
                  user_name));
        return WINBINDD_ERROR;
    }

    group_rid = user_info.info.id21->group_rid;
    unistr2_to_ascii(gecos_name, &user_info.info.id21->uni_full_name,
                     sizeof(gecos_name) - 1);

    free_samr_userinfo_ctr(&user_info);

    /* Resolve gid number */

    if (!winbindd_idmap_get_gid_from_rid(domain->name, group_rid, &gid)) {
        DEBUG(1, ("error getting group id for user %s\n", user_name));
        return WINBINDD_ERROR;
    }

    /* Fill in password structure */

    winbindd_fill_pwent(&state->response.data.pw, user_name, 
                        state->request.data.uid, gid, gecos_name);

    winbindd_fill_uid_cache_entry(domain->name, state->request.data.uid,
				  &state->response.data.pw);

    return WINBINDD_OK;
}

/*
 * set/get/endpwent functions
 */

/* Rewind file pointer for ntdom passwd database */

enum winbindd_result winbindd_setpwent(struct winbindd_cli_state *state)
{
    struct winbindd_domain *tmp;

    if (state == NULL) return WINBINDD_ERROR;
    
    /* Free old static data if it exists */

    if (state->getpwent_state != NULL) {
        free_getent_state(state->getpwent_state);
        state->getpwent_state = NULL;
    }

    /* Create sam pipes for each domain we know about */

    for(tmp = domain_list; tmp != NULL; tmp = tmp->next) {
        struct getent_state *domain_state;

        /* Skip domains other than WINBINDD_DOMAIN environment variable */

        if ((strcmp(state->request.domain, "") != 0) &&
            (strcmp(state->request.domain, tmp->name) != 0)) {
                continue;
        }

        /* Create a state record for this domain */

        if ((domain_state = (struct getent_state *)
             malloc(sizeof(struct getent_state))) == NULL) {

            return WINBINDD_ERROR;
        }

        ZERO_STRUCTP(domain_state);
        domain_state->domain = tmp;

        /* Add to list of open domains */

        DLIST_ADD(state->getpwent_state, domain_state)
    }

    return WINBINDD_OK;
}

/* Close file pointer to ntdom passwd database */

enum winbindd_result winbindd_endpwent(struct winbindd_cli_state *state)
{
    if (state == NULL) return WINBINDD_ERROR;

    free_getent_state(state->getpwent_state);    
    state->getpwent_state = NULL;

    return WINBINDD_OK;
}

/* Fetch next passwd entry from ntdom database */

enum winbindd_result winbindd_getpwent(struct winbindd_cli_state *state)
{
    if (state == NULL) return WINBINDD_ERROR;

    /* Process the current head of the getent_state list */

    while(state->getpwent_state != NULL) {
        struct getent_state *ent = state->getpwent_state;

        /* Get list of user entries for this pipe */

        if (!ent->got_sam_entries) {
            uint32 status, start_ndx = 0;

            /* Look in cache for entries, else get them direct */

            if (!winbindd_fetch_user_cache(ent->domain->name, 
                                           &ent->sam_entries,
                                           &ent->num_sam_entries)) {

                /* Fetch the user entries */

                if (!domain_handles_open(ent->domain)) goto cleanup;

                do {
                    status =
                        samr_enum_dom_users(
                            &ent->domain->sam_dom_handle, &start_ndx, 0, 0, 
                            0x10000, &ent->sam_entries, &ent->num_sam_entries);
                } while (status == STATUS_MORE_ENTRIES);

                /* Fill cache with received entries */
            
                winbindd_fill_user_cache(ent->domain->name, ent->sam_entries, 
                                         ent->num_sam_entries);
            }
            
            ent->got_sam_entries = True;
        }
        
        /* Send back a user */

        while (ent->sam_entry_index < ent->num_sam_entries) {
            enum winbindd_result result;
            fstring domain_user_name;
            char *user_name = (ent->sam_entries)
                [ent->sam_entry_index].acct_name; 
                
            /* Don't bother with machine accounts */

            if (user_name[strlen(user_name) - 1] == '$') {
                ent->sam_entry_index++;
                continue;
            }

            /* Prepend domain to name */

	    slprintf(domain_user_name, sizeof(domain_user_name),
		     "%s%s%s", ent->domain->name, lp_winbind_separator(), user_name);
                
            /* Get passwd entry from user name */
                
            fstrcpy(state->request.data.username, domain_user_name);
            result = winbindd_getpwnam_from_user(state);

            ent->sam_entry_index++;
                
            /* Return if user lookup worked */
                
            if (result == WINBINDD_OK) {
                return result;
            }
                
            /* Try next user */
            
            DEBUG(1, ("could not getpwnam_from_user for username %s\n",
                      domain_user_name));
        }

        /* We've exhausted all users for this pipe - close it down and
           start on the next one. */

    cleanup:

        /* Free mallocated memory for sam entries.  The data stored here
           may have been allocated from the cache. */

        if (ent->sam_entries != NULL) free(ent->sam_entries);
        ent->sam_entries = NULL;

        /* Free state information for this domain */

        {
            struct getent_state *old_ent;

            old_ent = state->getpwent_state;
            DLIST_REMOVE(state->getpwent_state, state->getpwent_state);
            free(old_ent);
        }
    }

    /* Out of pipes so we're done */

    return WINBINDD_ERROR;
}