/* 
   Unix SMB/Netbios implementation.
   Version 1.9.
   Groupname handling
   Copyright (C) Jeremy Allison 1998.
   
   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.
*/

/* 
 * UNIX gid and Local or Domain SID resolution.  This module resolves
 * only those entries in the map files, it is *NOT* responsible for
 * resolving UNIX groups not listed: that is an entirely different
 * matter, altogether...
 */

/*
 *
 *

 format of the file is:

 unixname	NT Group name
 unixname	Domain Admins (well-known Domain Group)
 unixname	DOMAIN_NAME\NT Group name
 unixname	OTHER_DOMAIN_NAME\NT Group name
 unixname	DOMAIN_NAME\Domain Admins (well-known Domain Group)
 ....

 if the DOMAIN_NAME\ component is left off, then your own domain is assumed.

 *
 *
 */


#include "includes.h"
extern int DEBUGLEVEL;

extern fstring global_sam_name;

/* we can map either local aliases or domain groups */
typedef enum 
{
	GROUP_LOCAL,
	GROUP_DOMAIN

} GROUP_TYPE;

/**************************************************************************
 Groupname map functionality. The code loads a groupname map file and
 (currently) loads it into a linked list. This is slow and memory
 hungry, but can be changed into a more efficient storage format
 if the demands on it become excessive.
***************************************************************************/

typedef struct group_name_info
{
   char *nt_name;
   char *nt_domain;
   char *unix_name;

   DOM_SID sid;
   gid_t  unix_gid;

} GROUP_NAME_INFO;

typedef struct name_map
{
	ubi_slNode next;
	GROUP_NAME_INFO grp;

} name_map_entry;

static ubi_slList groupname_map_list;
static ubi_slList aliasname_map_list;

static void delete_name_entry(name_map_entry *gmep)
{
	if (gmep->grp.nt_name)
	{
		free(gmep->grp.nt_name);
	}
	if (gmep->grp.nt_domain)
	{
		free(gmep->grp.nt_domain);
	}
	if (gmep->grp.unix_name)
	{
		free(gmep->grp.unix_name);
	}
	free((char*)gmep);
}

/**************************************************************************
 Delete all the entries in the name map list.
***************************************************************************/

static void delete_map_list(ubi_slList *map_list)
{
	name_map_entry *gmep;

	while ((gmep = (name_map_entry *)ubi_slRemHead(map_list )) != NULL)
	{
		delete_name_entry(gmep);
	}
}


/**************************************************************************
 makes a group sid out of a domain sid and a _unix_ gid.
***************************************************************************/
static BOOL make_mydomain_sid(GROUP_NAME_INFO *grp, GROUP_TYPE type)
{
	uint32 tmp_rid;
	DOM_SID sid;
	uint8  tmp_type;

	DEBUG(10,("make_mydomain_sid\n"));

	if (!map_domain_name_to_sid(&grp->sid, &(grp->nt_domain)))
	{
		DEBUG(0,("make_mydomain_sid: unknown domain %s\n",
			  grp->nt_domain));
		return False;
	}

	if (strequal(grp->nt_domain, global_sam_name) &&
	    lookup_wk_group_name(grp->nt_name, &sid, &tmp_type))
	{
		sid_copy(&grp->sid, &sid);
		return True;
	}
	else
	{
		if (type == GROUP_DOMAIN)
		{
			tmp_rid = pwdb_gid_to_group_rid(grp->unix_gid);
		}
		else
		{
			tmp_rid = pwdb_gid_to_alias_rid(grp->unix_gid);
		}
		return sid_append_rid(&(grp->sid), tmp_rid);
	}
}

/**************************************************************************
 makes a group sid out of an nt domain, nt group name or a unix group name.
***************************************************************************/
static BOOL unix_name_to_group_info(GROUP_NAME_INFO *grp, GROUP_TYPE type)
{
	struct group *gptr = NULL;

	/*
	 * Attempt to get the unix gid_t for this name.
	 */

	DEBUG(5,("unix_name_to_group_info: unix_name:%s\n", grp->unix_name));

	gptr = (struct group *)getgrnam(grp->unix_name);
	if (gptr == NULL)
	{
		DEBUG(0,("unix_name_to_group_info: getgrnam for group %s\
failed. Error was %s.\n", grp->unix_name, strerror(errno) ));
		return False;
	}

	grp->unix_gid = (gid_t)gptr->gr_gid;

	DEBUG(5,("unix_name_to_group_info: unix gid:%d\n", grp->unix_gid));

	/*
	 * Now map the name to an NT SID+RID.
	 */

	if (grp->nt_domain != NULL && !strequal(grp->nt_domain, global_sam_name))
	{
		/* Must add client-call lookup code here, to 
		 * resolve remote domain's sid and the group's rid,
		 * in that domain.
		 *
		 * NOTE: it is _incorrect_ to put code here that assumes
		 * that we can call pwdb_gid_to_group_rid() or _alias_rid():
		 * it is a totally different domain for which we are *NOT*
		 * responsible.
		 * for foriegn domains for which we are *NOT* the PDC, all
		 * we can be responsible for is the unix * gid_t to which
		 * the foriegn SID+rid maps to, on this _local_ machine.  
		 */

		if (!map_domain_name_to_sid(&grp->sid, &(grp->nt_domain)))
		{
			DEBUG(0,("unix_name_to_group_info: no known sid for %s\n",
				  grp->nt_domain));
			return False;
		}

		DEBUG(0,("unix_name_to_group_info: cannot resolve domain %s\n",
			  grp->nt_domain));

		return False;
	}
	else
	{
		return make_mydomain_sid(grp, type);
	}
}

static BOOL make_name_entry(name_map_entry **new_ep,
		char *nt_domain, char *nt_group, char *unix_group,
		GROUP_TYPE type)
{
	/*
	 * Create the list entry and add it onto the list.
	 */

	DEBUG(5,("make_name_entry:%s,%s,%s\n", nt_domain, nt_group, unix_group));

	(*new_ep) = (name_map_entry *)malloc(sizeof(name_map_entry));
	if ((*new_ep) == NULL)
	{
		DEBUG(0,("make_name_entry: malloc fail for name_map_entry.\n"));
		return False;
	} 

	ZERO_STRUCTP(*new_ep);

	(*new_ep)->grp.nt_name   = strdup(nt_group  );
	(*new_ep)->grp.nt_domain = strdup(nt_domain );
	(*new_ep)->grp.unix_name = strdup(unix_group);

	if ((*new_ep)->grp.nt_name   == NULL ||
	    (*new_ep)->grp.unix_name == NULL)
	{
		DEBUG(0,("make_name_entry: malloc fail for names in name_map_entry.\n"));
		delete_name_entry((*new_ep));
		return False;
	}

	/*
	 * look up the group names, make the Group-SID and unix gid
	 */
 
	if (!unix_name_to_group_info(&(*new_ep)->grp, type))
	{
		delete_name_entry((*new_ep));
		return False;
	}

	return True;
}

/**************************************************************************
 Load a name map file. Sets last accessed timestamp.
***************************************************************************/
static void load_name_map(GROUP_TYPE type)
{
	static time_t groupmap_file_last_modified = (time_t)0;
	static time_t aliasmap_file_last_modified = (time_t)0;
	static BOOL initialised_group = False;
	static BOOL initialised_alias = False;
	char *groupname_map_file = lp_groupname_map();
	char *aliasname_map_file = lp_aliasname_map();

	SMB_STRUCT_STAT st;
	FILE *fp;
	char *s;
	pstring buf;
	name_map_entry *new_ep;

	time_t *file_last_modified;
	int    *initialised;
	char   *map_file;
	ubi_slList *map_list;

	if (type == GROUP_DOMAIN)
	{
		file_last_modified = &groupmap_file_last_modified;
		initialised        = &initialised_group;
		map_file           = groupname_map_file;
		map_list           = &groupname_map_list;
	}
	else
	{
		file_last_modified = &aliasmap_file_last_modified;
		initialised        = &initialised_alias;
		map_file           = aliasname_map_file;
		map_list           = &aliasname_map_list;
	}

	DEBUG(10,("load_name_map : %s\n", map_file));

	if (!(*initialised))
	{
		ubi_slInitList(map_list);
		(*initialised) = True;
	}

	if (!*map_file)
	{
		return;
	}

	if (sys_stat(map_file, &st) != 0)
	{
		DEBUG(0, ("load_name_map: Unable to stat file %s. Error was %s\n",
		           map_file, strerror(errno) ));
		return;
	}

	/*
	 * Check if file has changed.
	 */
	if (st.st_mtime <= (*file_last_modified))
	{
		return;
	}

	(*file_last_modified) = st.st_mtime;

	/*
	 * Load the file.
	 */

	fp = fopen(map_file,"r");
	if (!fp)
	{
		DEBUG(0,("load_name_map: can't open name map %s. Error was %s\n",
		          map_file, strerror(errno)));
		return;
	}

	/*
	 * Throw away any previous list.
	 */
	delete_map_list(map_list);

	DEBUG(4,("load_name_map: Scanning name map %s\n",map_file));

	while ((s = fgets_slash(buf, sizeof(buf), fp)) != NULL)
	{
		pstring unixname;
		pstring nt_name;
		fstring nt_domain;
		fstring nt_group;
		char *p;

		DEBUG(10,("Read line |%s|\n", s));

		memset(nt_name, 0, sizeof(nt_name));

		if (!*s || strchr("#;",*s))
			continue;

		if (!next_token(&s,unixname, "\t\n\r=", sizeof(unixname)))
			continue;

		if (!next_token(&s,nt_name, "\t\n\r=", sizeof(nt_name)))
			continue;

		trim_string(unixname, " ", " ");
		trim_string(nt_name, " ", " ");

		if (!*nt_name)
			continue;

		if (!*unixname)
			continue;

		DEBUG(5,("unixname = %s, ntname = %s.\n",
		          unixname, nt_name));

		p = strchr(nt_name, '\\');

		if (p == NULL)
		{
			memset(nt_domain, 0, sizeof(nt_domain));
			fstrcpy(nt_group, nt_name);
		}
		else
		{
			*p = 0;
			p++;
			fstrcpy(nt_domain, nt_name);
			fstrcpy(nt_group , p);
		}

		if (make_name_entry(&new_ep, nt_domain, nt_name, unixname, type))
		{
			ubi_slAddHead(map_list, (ubi_slNode *)new_ep);
		}
	}

	DEBUG(10,("load_name_map: Added %ld entries to name map.\n",
	ubi_slCount(map_list)));

	fclose(fp);
}

/***********************************************************
 Lookup by SID
************************************************************/
static BOOL map_sid(GROUP_TYPE type, ubi_slList *map_list,
		DOM_SID *psid, gid_t *gid, char *ntname, char *ntdomain)
{
	name_map_entry *gmep;

	/*
	 * Initialize and load if not already loaded.
	 */
	load_name_map(type);

	for (gmep = (name_map_entry *)ubi_slFirst(map_list);
	     gmep != NULL;
	     gmep = (name_map_entry *)ubi_slNext(gmep ))
	{
		if (sid_equal(&gmep->grp.sid, psid))
		{
			if (gid != NULL)
			{
				*gid = gmep->grp.unix_gid;
			}
			if (ntname != NULL)
			{
				fstrcpy(ntname, gmep->grp.nt_name);
			}
			if (ntdomain != NULL)
			{
				fstrcpy(ntdomain, gmep->grp.nt_domain);
			}
			DEBUG(7,("map_sid: Mapping unix group %s to nt group %s.\n",
			       gmep->grp.unix_name, gmep->grp.nt_name ));
			return True;
		}
	}

	return False;
}

/***********************************************************
 Lookup nt name.
************************************************************/
static BOOL map_ntname(GROUP_TYPE type, ubi_slList *map_list,
		char *ntname, char *ntdomain, DOM_SID *psid,
		char *unixname, gid_t *gid)
{
	name_map_entry *gmep;

	/*
	 * Initialize and load if not already loaded.
	 */
	load_name_map(type);

	for (gmep = (name_map_entry *)ubi_slFirst(&map_list);
	     gmep != NULL;
	     gmep = (name_map_entry *)ubi_slNext(gmep ))
	{
		if (strequal(gmep->grp.nt_name  , ntname) &&
		    strequal(gmep->grp.nt_domain, ntdomain))
		{
			if (psid != NULL)
			{
				*psid = gmep->grp.sid;
			}
			if (gid != NULL)
			{
				*gid = gmep->grp.unix_gid;
			}
			if (unixname != NULL)
			{
				fstrcpy(unixname, gmep->grp.unix_name);
			}
			DEBUG(7,("map_ntname: Mapping unix group %s to nt group %s.\n",
			       gmep->grp.unix_name, gmep->grp.nt_name ));
			return True;
		}
	}

	return False;
}

/***********************************************************
 Lookup unix name.
************************************************************/
static BOOL map_unixname(GROUP_TYPE type, ubi_slList *map_list,
		char *unixname, DOM_SID *psid, char *ntname, char *ntdomain)
{
	name_map_entry *gmep;

	/*
	 * Initialize and load if not already loaded.
	 */
	load_name_map(type);

	for (gmep = (name_map_entry *)ubi_slFirst(&map_list);
	     gmep != NULL;
	     gmep = (name_map_entry *)ubi_slNext(gmep ))
	{
		if (strequal(gmep->grp.unix_name, unixname))
		{
			if (psid != NULL)
			{
				*psid = gmep->grp.sid;
			}
			if (ntname != NULL)
			{
				fstrcpy(ntname, gmep->grp.nt_name);
			}
			if (ntdomain != NULL)
			{
				fstrcpy(ntdomain, gmep->grp.nt_domain);
			}
			DEBUG(7,("map_unixname: Mapping unix group %s to nt group %s.\n",
			       gmep->grp.unix_name, gmep->grp.nt_name ));
			return True;
		}
	}

	return False;
}

/***********************************************************
 Lookup by gid_t.
************************************************************/
static BOOL map_gid(GROUP_TYPE type, ubi_slList *map_list,
		gid_t gid, DOM_SID *psid, char *ntname, char *ntdomain)
{
	name_map_entry *gmep;

	/*
	 * Initialize and load if not already loaded.
	 */
	load_name_map(type);

	for (gmep = (name_map_entry *)ubi_slFirst(&map_list);
	     gmep != NULL;
	     gmep = (name_map_entry *)ubi_slNext(gmep ))
	{
		if (gmep->grp.unix_gid == gid)
		{
			if (psid != NULL)
			{
				*psid = gmep->grp.sid;
			}
			if (ntname != NULL)
			{
				fstrcpy(ntname, gmep->grp.nt_name);
			}
			if (ntdomain != NULL)
			{
				fstrcpy(ntdomain, gmep->grp.nt_domain);
			}
			DEBUG(7,("map_gid: Mapping unix group %s to nt group %s.\n",
			       gmep->grp.unix_name, gmep->grp.nt_name ));
			return True;
		}
	}

	return False;
}

/***********************************************************
 *
 * Call four functions to resolve unix group ids and either
 * local group SIDs or domain group SIDs listed in the local group
 * or domain group map files.
 *
 * Note that it is *NOT* the responsibility of these functions to
 * resolve entries that are not in the map files.
 *
 * Any SID can be in the map files (i.e from any Domain).
 *
 ***********************************************************/

/***********************************************************
 Lookup a Group entry by sid.
************************************************************/
BOOL map_group_sid(DOM_SID *psid, gid_t *gid, char *group_name, char *nt_domain)
{
	return map_sid(GROUP_DOMAIN, &groupname_map_list, psid, gid, group_name, nt_domain);
}

/***********************************************************
 Lookup an Alias SID entry by name.
************************************************************/
BOOL map_alias_sid(DOM_SID *psid, gid_t *gid, char *alias_name, char *nt_domain)
{
	return map_sid(GROUP_LOCAL, &aliasname_map_list, psid, gid, alias_name, nt_domain);
}

/***********************************************************
 Lookup a UNIX Group entry by name.
************************************************************/
BOOL map_unix_group_name(char *group_name, DOM_SID *psid, char *ntgroup_name, char *nt_domain)
{
	return map_unixname(GROUP_DOMAIN, &groupname_map_list, group_name, psid, ntgroup_name, nt_domain);
}

/***********************************************************
 Lookup a UNIX Alias entry by name.
************************************************************/
BOOL map_unix_alias_name(char *alias_name, DOM_SID *psid, char *ntalias_name, char *nt_domain)
{
	return map_unixname(GROUP_LOCAL, &aliasname_map_list, alias_name, psid, ntalias_name, nt_domain);
}

/***********************************************************
 Lookup a Group entry
************************************************************/
BOOL map_nt_group_name(char *ntgroup_name, char *nt_domain, DOM_SID *psid, char *group_name, gid_t *gid)
{
	return map_ntname(GROUP_DOMAIN, &groupname_map_list, ntgroup_name, nt_domain, psid, group_name, gid);
}

/***********************************************************
 Lookup an Alias name entry 
************************************************************/
BOOL map_nt_alias_name(char *ntalias_name, char *nt_domain, DOM_SID *psid, char *alias_name, gid_t *gid)
{
	return map_ntname(GROUP_LOCAL, &aliasname_map_list, ntalias_name, nt_domain, psid, alias_name, gid);
}

/***********************************************************
 Lookup an Alias SID entry by gid_t.
************************************************************/
BOOL map_alias_gid(gid_t gid, DOM_SID *psid, char *nt_als_name, char *nt_domain)
{
	return map_gid(GROUP_LOCAL, &aliasname_map_list, gid, psid, nt_als_name, nt_domain);
}

/***********************************************************
 Lookup a Group SID entry by gid_t.
************************************************************/
BOOL map_group_gid( gid_t gid, DOM_SID *psid, char *nt_grp_name, char *nt_domain)
{
	return map_gid(GROUP_DOMAIN, &groupname_map_list, gid, psid, nt_grp_name, nt_domain);
}