/* 
   Unix SMB/Netbios implementation.
   Version 2.0

   Winbind daemon for ntdom nss module

   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"

BOOL domain_handles_open(struct winbindd_domain *domain)
{
	return domain->sam_handle_open &&
		domain->sam_dom_handle_open &&
		rpc_hnd_ok(&domain->sam_handle) &&
		rpc_hnd_ok(&domain->sam_dom_handle);
}

static BOOL resolve_dc_name(char *domain_name, fstring domain_controller)
{
	struct in_addr ip;
	extern pstring global_myname;
	
	/* if its our primary domain and password server is not '*' then use the
	   password server parameter */
	if (strcmp(domain_name,lp_workgroup()) == 0 && !lp_wildcard_dc()) {
		fstrcpy(domain_controller, lp_passwordserver());
		return True;
	}

	if (!resolve_name(domain_name, &ip, 0x1B)) return False;

	return lookup_pdc_name(global_myname, domain_name, &ip, 
			       domain_controller);
}

static struct winbindd_domain *add_trusted_domain(char *domain_name)
{
    struct winbindd_domain *domain;

    DEBUG(1, ("adding trusted domain %s\n", domain_name));

    /* Create new domain entry */

    if ((domain = (struct winbindd_domain *)malloc(sizeof(*domain))) == NULL) {
        return NULL;
    }

    /* Fill in fields */

    ZERO_STRUCTP(domain);

    if (domain_name) {
        fstrcpy(domain->name, domain_name);
    }

    /* Link to domain list */

    DLIST_ADD(domain_list, domain);

    return domain;
}

/* Look up global info for the winbind daemon */
static BOOL get_trusted_domains(void)
{
	uint32 enum_ctx = 0;
	uint32 num_doms = 0;
	char **domains = NULL;
	DOM_SID **sids = NULL;
	BOOL result;
	int i;
	
	DEBUG(1, ("getting trusted domain list\n"));

	/* Add our workgroup - keep handle to look up trusted domains */
	if (!add_trusted_domain(lp_workgroup())) {
		DEBUG(0, ("could not add record for domain %s\n", 
			  lp_workgroup()));
		return False;
	}
	
	/* Enumerate list of trusted domains */	
	result = lsa_enum_trust_dom(&server_state.lsa_handle, &enum_ctx,
				    &num_doms, &domains, &sids);
	
	if (!result || !domains) return False;
	
        /* Add each domain to the trusted domain list */
	for(i = 0; i < num_doms; i++) {
		if (!add_trusted_domain(domains[i])) {
			DEBUG(0, ("could not add record for domain %s\n", 
				  domains[i]));
			result = False;
		}
	}
	
	/* Free memory */	
	free_char_array(num_doms, domains);
	free_sid_array(num_doms, sids);
	
	return True;
}


/* Open sam and sam domain handles to a domain and cache the results */
static BOOL open_sam_handles(struct winbindd_domain *domain)
{
	/* Get domain info */
	if (!domain->got_domain_info) {
		domain->got_domain_info = get_domain_info(domain);
		if (!domain->got_domain_info) return False;
	}

	if ((domain->sam_handle_open && !rpc_hnd_ok(&domain->sam_handle)) ||
	    (domain->sam_dom_handle_open && 
	     !rpc_hnd_ok(&domain->sam_dom_handle))) {

		domain->got_domain_info = get_domain_info(domain);
		if (domain->sam_dom_handle_open) {
			samr_close(&domain->sam_dom_handle);
			domain->sam_dom_handle_open = False;
		}
		if (domain->sam_handle_open) {
			samr_close(&domain->sam_handle);
			domain->sam_handle_open = False;
		}
	}

	/* Open sam handle if it isn't already open */

	if (!domain->sam_handle_open) {

		domain->sam_handle_open = 
			samr_connect(domain->controller, 
				     SEC_RIGHTS_MAXIMUM_ALLOWED, 
				     &domain->sam_handle);

		if (!domain->sam_handle_open) return False;
	}

	/* Open sam domain handle if it isn't already open */

	if (!domain->sam_dom_handle_open) {

		domain->sam_dom_handle_open =
			samr_open_domain(&domain->sam_handle, 
					 SEC_RIGHTS_MAXIMUM_ALLOWED, 
					 &domain->sid, &domain->sam_dom_handle);

		if (!domain->sam_dom_handle_open) return False;
	}
	
	return True;
}

/* Close all LSA and SAM connections */

static void winbindd_kill_connections(void)
{
	struct winbindd_cli_state *cli;
	struct winbindd_domain *domain;

	DEBUG(1,("killing winbindd connections\n"));

	/* Close LSA connection */

	server_state.pwdb_initialised = False;
	server_state.lsa_handle_open = False;
	lsa_close(&server_state.lsa_handle);
	
	/* Close SAM connections */

	domain = domain_list;

	while(domain) {
		struct winbindd_domain *next;

		/* Close SAM handles */

		if (domain->sam_dom_handle_open) {
			samr_close(&domain->sam_dom_handle);
			domain->sam_dom_handle_open = False;
		}

		if (domain->sam_handle_open) {
			samr_close(&domain->sam_handle);
			domain->sam_handle_open = False;
		}

		/* Remove from list */

		next = domain->next;
		DLIST_REMOVE(domain_list, domain);
		free(domain);

		domain = next;
	}

	/* We also need to go through and trash any pointers to domains in
	   get{pw,gr}ent state records */

	for (cli = client_list; cli; cli = cli->next) {
		free_getent_state(cli->getpwent_state);
		free_getent_state(cli->getgrent_state);
	}
}

/* Try to establish connections to NT servers */

void establish_connections(void) 
{
	struct winbindd_domain *domain;
	static time_t lastt;
	time_t t;

	t = time(NULL);
	if (t - lastt < WINBINDD_ESTABLISH_LOOP) return;
	lastt = t;

	/* maybe the connection died - if so then close up and restart */
	if (server_state.pwdb_initialised &&
	    server_state.lsa_handle_open &&
	    !rpc_hnd_ok(&server_state.lsa_handle)) {
		winbindd_kill_connections();
	}

	if (!server_state.pwdb_initialised) {
		fstrcpy(server_state.controller, lp_passwordserver());
		if (lp_wildcard_dc()) {
			if (!resolve_dc_name(lp_workgroup(), server_state.controller)) {
				return;
			}
		}

		server_state.pwdb_initialised = pwdb_initialise(False);
		if (!server_state.pwdb_initialised) return;
	}

	/* Open lsa handle if it isn't already open */
	if (!server_state.lsa_handle_open) {
		server_state.lsa_handle_open =
			lsa_open_policy(server_state.controller, &server_state.lsa_handle, 
					False, SEC_RIGHTS_MAXIMUM_ALLOWED);
		if (!server_state.lsa_handle_open) return;

		/* now we can talk to the server we can get some info */
		get_trusted_domains();
	}

	for (domain=domain_list; domain; domain=domain->next) {
		if (!domain_handles_open(domain)) {
			open_sam_handles(domain);
		}
	}
}


/* Connect to a domain controller using get_any_dc_name() to discover 
   the domain name and sid */
BOOL lookup_domain_sid(char *domain_name, struct winbindd_domain *domain)
{
    fstring level5_dom;
    BOOL res;
    uint32 enum_ctx = 0;
    uint32 num_doms = 0;
    char **domains = NULL;
    DOM_SID **sids = NULL;

    if (domain == NULL) {
        return False;
    }

    DEBUG(1, ("looking up sid for domain %s\n", domain_name));

    /* Get controller name for domain */
    if (!resolve_dc_name(domain_name, domain->controller)) {
	    return False;
    }

    if (strequal(domain->controller, server_state.controller)) {
	    /* Do a level 5 query info policy */
	    return lsa_query_info_pol(&server_state.lsa_handle, 0x05, 
				      level5_dom, &domain->sid);
    } 

    /* Use lsaenumdomains to get sid for this domain */
    
    res = lsa_enum_trust_dom(&server_state.lsa_handle, &enum_ctx,
			     &num_doms, &domains, &sids);
    
    /* Look for domain name */
    
    if (res && domains && sids) {
            int found = False;
            int i;
	    
            for(i = 0; i < num_doms; i++) {
		    if (strequal(domain_name, domains[i])) {
			    sid_copy(&domain->sid, sids[i]);
			    found = True;
			    break;
		    }
            }
	    
            res = found;
    }
    
    /* Free memory */
    
    free_char_array(num_doms, domains);
    free_sid_array(num_doms, sids);

    return res;
}


/* Lookup domain controller and sid for a domain */

BOOL get_domain_info(struct winbindd_domain *domain)
{
    fstring sid_str;

    DEBUG(1, ("Getting domain info for domain %s\n", domain->name));

    /* Lookup domain sid */        
    if (!lookup_domain_sid(domain->name, domain)) {
	    DEBUG(0, ("could not find sid for domain %s\n", domain->name));
	    return False;
    }
    
    /* Lookup OK */

    domain->got_domain_info = 1;

    sid_to_string(sid_str, &domain->sid);
    DEBUG(1, ("found sid %s for domain %s\n", sid_str, domain->name));

    return True;
}        

/* Lookup a sid in a domain from a name */

BOOL winbindd_lookup_sid_by_name(struct winbindd_domain *domain,
                                 char *name, DOM_SID *sid,
                                 enum SID_NAME_USE *type)
{
    int num_sids = 0, num_names = 1;
    DOM_SID *sids = NULL;
    uint32 *types = NULL;
    BOOL res;

    /* Don't bother with machine accounts */

    if (name[strlen(name) - 1] == '$') {
        return False;
    }

    /* Lookup name */

    res = lsa_lookup_names(&server_state.lsa_handle, num_names, (char **)&name,
			   &sids, &types, &num_sids);

    /* Return rid and type if lookup successful */

    if (res) {

        /* Return sid */

        if ((sid != NULL) && (sids != NULL)) {
            sid_copy(sid, &sids[0]);
        }

        /* Return name type */

        if ((type != NULL) && (types != NULL)) {
            *type = types[0];
        }
    }
    
    /* Free memory */

    if (types != NULL) free(types);
    if (sids != NULL) free(sids);

    return res;
}

/* Lookup a name in a domain from a sid */

BOOL winbindd_lookup_name_by_sid(struct winbindd_domain *domain,
                                 DOM_SID *sid, char *name,
                                 enum SID_NAME_USE *type)
{
    int num_sids = 1, num_names = 0;
    uint32 *types = NULL;
    char **names;
    BOOL res;

    /* Lookup name */
    res = lsa_lookup_sids(&server_state.lsa_handle, num_sids, &sid, &names, 
			  &types, &num_names);

    /* Return name and type if successful */

    if (res) {

        /* Return name */

        if ((names != NULL) && (name != NULL)) {
            fstrcpy(name, names[0]);
        }

        /* Return name type */

        if ((type != NULL) && (types != NULL)) {
            *type = types[0];
        }
    }

    /* Free memory */

    safe_free(types);
    free_char_array(num_names, names);

    return res;
}

/* Lookup user information from a rid */

BOOL winbindd_lookup_userinfo(struct winbindd_domain *domain,
                              uint32 user_rid, SAM_USERINFO_CTR *user_info)
{
	if (!domain_handles_open(domain)) return False;

	return get_samr_query_userinfo(&domain->sam_dom_handle, 0x15, user_rid, user_info);
}                                   

/* Lookup group information from a rid */

BOOL winbindd_lookup_groupinfo(struct winbindd_domain *domain,
                              uint32 group_rid, GROUP_INFO_CTR *info)
{
	if (!domain_handles_open(domain)) return False;

	return get_samr_query_groupinfo(&domain->sam_dom_handle, 1, group_rid, info);
}

/* Lookup group membership given a rid */

BOOL winbindd_lookup_groupmem(struct winbindd_domain *domain,
                              uint32 group_rid, uint32 *num_names, 
                              uint32 **rid_mem, char ***names, 
                              enum SID_NAME_USE **name_types)
{
	if (!domain_handles_open(domain)) return False;
    
	return sam_query_groupmem(&domain->sam_dom_handle, group_rid, num_names, 
				  rid_mem, names, name_types);
}

/* Lookup alias membership given a rid */

int winbindd_lookup_aliasmem(struct winbindd_domain *domain,
                             uint32 alias_rid, uint32 *num_names, 
                             DOM_SID ***sids, char ***names, 
                             enum SID_NAME_USE **name_types)
{
    /* Open sam handles */
    if (!domain_handles_open(domain)) return False;

    return sam_query_aliasmem(domain->controller, 
			      &domain->sam_dom_handle, alias_rid, num_names, 
			      sids, names, name_types);
}

/* Globals for domain list stuff */

struct winbindd_domain *domain_list = NULL;

/* Given a domain name, return the struct winbindd domain info for it */

struct winbindd_domain *find_domain_from_name(char *domain_name)
{
	struct winbindd_domain *tmp;

	/* Search through list */
	for (tmp = domain_list; tmp != NULL; tmp = tmp->next) {
		if (strcmp(domain_name, tmp->name) == 0) {
			if (!tmp->got_domain_info) return NULL;
                        return tmp;
                }
        }

	/* Not found */
	return NULL;
}

/* Free state information held for {set,get,end}{pw,gr}ent() functions */

void free_getent_state(struct getent_state *state)
{
    struct getent_state *temp;

    /* Iterate over state list */

    temp = state;

    while(temp != NULL) {
        struct getent_state *next;

        /* Free sam entries then list entry */

        safe_free(state->sam_entries);
        DLIST_REMOVE(state, state);
        next = temp->next;

        free(temp);
        temp = next;
    }
}

/* Parse list of arguments to winbind uid or winbind gid parameters */

static BOOL parse_id_list(char *paramstr, BOOL is_user)
{
    uid_t id_low, id_high = 0;

    /* Give a nicer error message if no parameters specified */

    if (strequal(paramstr, "")) {
        DEBUG(0, ("winbid %s parameter missing\n", is_user ? "uid" : "gid"));
        return False;
    }
    
    /* Parse entry */

    if (sscanf(paramstr, "%u-%u", &id_low, &id_high) != 2) {
        DEBUG(0, ("winbid %s parameter invalid\n", 
                  is_user ? "uid" : "gid"));
        return False;
    }
    
    /* Store id info */
    
    if (is_user) {
        server_state.uid_low = id_low;
        server_state.uid_high = id_high;
    } else {
        server_state.gid_low = id_low;
        server_state.gid_high = id_high;
    }

    return True;
}

/* Initialise trusted domain info */

BOOL winbindd_param_init(void)
{
    /* Parse winbind uid and winbind_gid parameters */

    if (!(parse_id_list(lp_winbind_uid(), True) &&
          parse_id_list(lp_winbind_gid(), False))) {
        return False;
    }

    /* Check for reversed uid and gid ranges */
        
    if (server_state.uid_low > server_state.uid_high) {
        DEBUG(0, ("uid range invalid\n"));
        return False;
    }
    
    if (server_state.gid_low > server_state.gid_high) {
        DEBUG(0, ("gid range for invalid\n"));
        return False;
    }
    
    return True;
}

/* Convert a enum winbindd_cmd to a string */

char *winbindd_cmd_to_string(enum winbindd_cmd cmd)
{
    char *result;

    switch (cmd) {

    case WINBINDD_GETPWNAM_FROM_USER:
        result = "getpwnam from user";
        break;
            
    case WINBINDD_GETPWNAM_FROM_UID:
        result = "getpwnam from uid";
        break;

    case WINBINDD_GETGRNAM_FROM_GROUP:
        result = "getgrnam from group";
        break;

    case WINBINDD_GETGRNAM_FROM_GID:
        result = "getgrnam from gid";
        break;

    case WINBINDD_SETPWENT:
        result = "setpwent";
        break;

    case WINBINDD_ENDPWENT:
        result = "endpwent";
        break;

    case WINBINDD_GETPWENT:
        result = "getpwent";
        break;

    case WINBINDD_SETGRENT:
        result = "setgrent";
        break;

    case WINBINDD_ENDGRENT:
        result = "endgrent"; 
        break;

    case WINBINDD_GETGRENT:
        result = "getgrent";
        break;

    case WINBINDD_PAM_AUTH:
        result = "pam_auth";
        break;

    default:
        result = "invalid command";
        break;
    }

    return result;
};


/* parse a string of the form DOMAIN/user into a domain and a user */
void parse_domain_user(char *domuser, fstring domain, fstring user)
{
	char *p;
	char *sep = lp_winbind_separator();
	if (!sep) sep = "\\";
	p = strchr(domuser,*sep);
	if (!p) p = strchr(domuser,'\\');
	if (!p) {
		fstrcpy(domain,"");
		fstrcpy(user, domuser);
		return;
	}
	
	fstrcpy(user, p+1);
	fstrcpy(domain, domuser);
	domain[PTR_DIFF(p, domuser)] = 0;
}

/* find the sequence number for a domain */
uint32 domain_sequence_number(char *domain_name)
{
	struct winbindd_domain *domain;
	SAM_UNK_CTR ctr;

	domain = find_domain_from_name(domain_name);
	if (!domain) return DOM_SEQUENCE_NONE;

	if (!samr_query_dom_info(&domain->sam_dom_handle, 2, &ctr)) {
		DEBUG(2,("domain sequence query failed\n"));
		return DOM_SEQUENCE_NONE;
	}

	DEBUG(4,("got domain sequence number for %s of %u\n", 
		 domain_name, (unsigned)ctr.info.inf2.seq_num));
	
	return ctr.info.inf2.seq_num;
}