/* 
   Unix SMB/Netbios implementation.
   Version 1.9.
   Password and authentication handling
   Copyright (C) Jeremy Allison 1996-1998
   Copyright (C) Luke Kenneth Casson Leighton 1996-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.
*/

#include "includes.h"
#include "nterr.h"

extern int DEBUGLEVEL;
extern DOM_SID global_sam_sid;

/*
 * NOTE. All these functions are abstracted into a structure
 * that points to the correct function for the selected database. JRA.
 *
 * NOTE.  for the get/mod/add functions, there are two sets of functions.
 * one supports struct sam_passwd, the other supports struct smb_passwd.
 * for speed optimisation it is best to support both these sets.
 * 
 * it is, however, optional to support one set but not the other: there
 * is conversion-capability built in to passdb.c, and run-time error
 * detection for when neither are supported.
 * 
 * password database writers are recommended to implement the sam_passwd
 * functions in a first pass, as struct sam_passwd contains more
 * information, needed by the NT Domain support.
 * 
 * an API writer is expected to create either one set (struct smb_passwd) or
 * the other (struct sam_passwd) OR both, and optionally also to write display
 * info routines * (struct sam_disp_info).  functions which the API writer
 * chooses NOT to write must be wrapped in conversion functions (pwdb_x_to_y)
 * such that API users can call any function and still get valid results.
 *
 * the password API does NOT fill in the gaps if you set an API function
 * to NULL: it will deliberately attempt to call the NULL function.
 *
 */

static struct sam_passdb_ops *pwdb_ops;

/***************************************************************
 Initialise the password db operations.
***************************************************************/

BOOL initialise_sam_password_db(void)
{
  if (pwdb_ops)
  {
    return True;
  }

#ifdef WITH_NISPLUS
  pwdb_ops =  nisplus_initialise_sam_password_db();
#elif defined(WITH_LDAP)
  pwdb_ops = ldap_initialise_sam_password_db();
#elif defined(USE_SMBPASS_DB)
  pwdb_ops = file_initialise_sam_password_db();
#endif 

  return (pwdb_ops != NULL);
}

/*
 * Functions that return/manipulate a struct sam_passwd.
 */

/***************************************************************
 Start to enumerate the smb or sam passwd list. Returns a void pointer
 to ensure no modification outside this module.

 Note that currently it is being assumed that a pointer returned
 from this function may be used to enumerate struct sam_passwd
 entries as well as struct smb_passwd entries. This may need
 to change. JRA.

****************************************************************/

void *startsam21pwent(BOOL update)
{
  return pwdb_ops->startsam21pwent(update);
}

/***************************************************************
 End enumeration of the sam passwd list.

 Note that currently it is being assumed that a pointer returned
 from this function may be used to enumerate struct sam_passwd
 entries as well as struct smb_passwd entries. This may need
 to change. JRA.

****************************************************************/

void endsam21pwent(void *vp)
{
  pwdb_ops->endsam21pwent(vp);
}

/*************************************************************************
 Routine to return the next entry in the smb passwd list.
 *************************************************************************/

struct sam_passwd *getsam21pwent(void *vp)
{
	return pwdb_sam_map_names(pwdb_ops->getsam21pwent(vp));
}

/************************************************************************
 Utility function to search sam passwd by name.  use this if your database
 does not have search facilities.
*************************************************************************/

struct sam_passwd *iterate_getsam21pwntnam(const char *ntname)
{
	fstring nt_name;
	struct sam_passwd *pwd = NULL;
	void *fp = NULL;

	DEBUG(10, ("search by name: %s\n", ntname));

	fstrcpy(nt_name, ntname);

	/* Open the smb password database - not for update. */
	fp = startsmbpwent(False);

	if (fp == NULL)
	{
		DEBUG(0, ("unable to open sam password database.\n"));
		return NULL;
	}

	while ((pwd = getsam21pwent(fp)) != NULL && !strequal(pwd->nt_name, nt_name))
	{
		DEBUG(10, ("iterate: %s 0x%x\n", pwd->nt_name, pwd->user_rid));
	}

	if (pwd != NULL)
	{
		DEBUG(10, ("found by name: %s\n", nt_name));
	}

	endsmbpwent(fp);
	return pwd;
}

/************************************************************************
 Utility function to search sam passwd by rid.  use this if your database
 does not have search facilities.

 search capability by both rid and uid are needed as the rid <-> uid
 mapping may be non-monotonic.  

*************************************************************************/

struct sam_passwd *iterate_getsam21pwrid(uint32 rid)
{
	struct sam_passwd *pwd = NULL;
	void *fp = NULL;

	DEBUG(10, ("search by rid: %x\n", rid));

	/* Open the smb password file - not for update. */
	fp = startsmbpwent(False);

	if (fp == NULL)
	{
		DEBUG(0, ("unable to open sam password database.\n"));
		return NULL;
	}

	while ((pwd = getsam21pwent(fp)) != NULL && pwd->user_rid != rid)
	{
		DEBUG(10, ("iterate: %s 0x%x\n", pwd->nt_name, pwd->user_rid));
	}

	if (pwd != NULL)
	{
		DEBUG(10, ("found by user_rid: %x\n", rid));
	}

	endsmbpwent(fp);
	return pwd;
}

/************************************************************************
 Utility function to search sam passwd by uid.  use this if your database
 does not have search facilities.

 search capability by both rid and uid are needed as the rid <-> uid
 mapping may be non-monotonic.  

*************************************************************************/

struct sam_passwd *iterate_getsam21pwuid(uid_t uid)
{
	struct sam_passwd *pwd = NULL;
	void *fp = NULL;

	DEBUG(10, ("search by uid: %x\n", (int)uid));

	/* Open the smb password file - not for update. */
	fp = startsmbpwent(False);

	if (fp == NULL)
	{
		DEBUG(0, ("unable to open sam password database.\n"));
		return NULL;
	}

	while ((pwd = getsam21pwent(fp)) != NULL && pwd->unix_uid != uid)
	{
	}

	if (pwd != NULL)
	{
		DEBUG(10, ("found by unix_uid: %x\n", (int)uid));
	}

	endsmbpwent(fp);
	return pwd;
}

/*************************************************************************
 Routine to return a display info structure, by rid
 *************************************************************************/
struct sam_disp_info *getsamdisprid(uint32 rid)
{
	return pwdb_ops->getsamdisprid(rid);
}

/************************************************************************
 Routine to search sam passwd by name.
*************************************************************************/

struct sam_passwd *getsam21pwntnam(const char *name)
{
	return pwdb_sam_map_names(pwdb_ops->getsam21pwntnam(name));
}

/************************************************************************
 Routine to search sam passwd by rid.  
*************************************************************************/

struct sam_passwd *getsam21pwrid(uint32 rid)
{
	return pwdb_sam_map_names(pwdb_ops->getsam21pwrid(rid));
}


/**********************************************************
 **********************************************************

 utility routines which are likely to be useful to all password
 databases

 **********************************************************
 **********************************************************/

/*************************************************************
 initialises a struct sam_disp_info.
 **************************************************************/

static void pwdb_init_dispinfo(struct sam_disp_info *user)
{
	if (user == NULL) return;
	bzero(user, sizeof(*user));
	user->user_rid = 0xffffffff;
}

/*************************************************************
 initialises a struct sam_passwd.
 **************************************************************/
void pwdb_init_sam(struct sam_passwd *user)
{
	if (user == NULL) return;
	bzero(user, sizeof(*user));
	unix_to_nt_time(&user->logon_time            , (time_t)-1);
	unix_to_nt_time(&user->logoff_time           , (time_t)-1);
	unix_to_nt_time(&user->kickoff_time          , (time_t)-1);
	unix_to_nt_time(&user->pass_last_set_time    , (time_t)-1);
	unix_to_nt_time(&user->pass_can_change_time  , (time_t)-1);
	unix_to_nt_time(&user->pass_must_change_time , (time_t)-1);

	user->unix_uid = (uid_t)-1;
	user->unix_gid = (gid_t)-1;
	user->user_rid  = 0xffffffff;
	user->group_rid = 0xffffffff;
}

/*************************************************************************
 Routine to return the next entry in the sam passwd list.
 *************************************************************************/

struct sam_disp_info *pwdb_sam_to_dispinfo(struct sam_passwd *user)
{
	static struct sam_disp_info disp_info;

	if (user == NULL) return NULL;

	pwdb_init_dispinfo(&disp_info);

	disp_info.nt_name   = user->nt_name;
	disp_info.full_name = user->full_name;
	disp_info.user_rid  = user->user_rid;

	return &disp_info;
}

/*************************************************************
 converts a sam_passwd structure to a smb_passwd structure.
 **************************************************************/

struct smb_passwd *pwdb_sam_to_smb(struct sam_passwd *user)
{
	static struct smb_passwd pw_buf;
	static fstring nt_name;
	static fstring unix_name;

	if (user == NULL) return NULL;

	pwdb_init_smb(&pw_buf);

	fstrcpy(nt_name  , user->nt_name);
	fstrcpy(unix_name, user->unix_name);
	pw_buf.nt_name            = nt_name;
	pw_buf.unix_name          = unix_name;
	pw_buf.unix_uid           = user->unix_uid;
	pw_buf.user_rid           = user->user_rid;
	pw_buf.smb_passwd         = user->smb_passwd;
	pw_buf.smb_nt_passwd      = user->smb_nt_passwd;
	pw_buf.acct_ctrl          = user->acct_ctrl;
	pw_buf.pass_last_set_time = nt_time_to_unix(&user->pass_last_set_time);

	return &pw_buf;
}


/*************************************************************
 converts a smb_passwd structure to a sam_passwd structure.
 **************************************************************/

struct sam_passwd *pwdb_smb_to_sam(struct smb_passwd *user)
{
	static struct sam_passwd pw_buf;
	static fstring nt_name;
	static fstring unix_name;

	if (user == NULL) return NULL;

	pwdb_init_sam(&pw_buf);

	fstrcpy(nt_name  , user->nt_name);
	fstrcpy(unix_name, user->unix_name);
	pw_buf.nt_name            = nt_name;
	pw_buf.unix_name          = unix_name;
	pw_buf.unix_uid           = user->unix_uid;
	pw_buf.user_rid           = user->user_rid;
	pw_buf.smb_passwd         = user->smb_passwd;
	pw_buf.smb_nt_passwd      = user->smb_nt_passwd;
	pw_buf.acct_ctrl          = user->acct_ctrl;
	unix_to_nt_time(&pw_buf.pass_last_set_time, user->pass_last_set_time);

	return &pw_buf;
}

static BOOL trust_account_warning_done = False;

/*************************************************************
 fills in missing details.  one set of details _must_ exist.
 **************************************************************/
struct sam_passwd *pwdb_sam_map_names(struct sam_passwd *sam)
{
	DOM_NAME_MAP gmep;
	BOOL found = False;
	DOM_SID sid;
	static fstring unix_name;
	static fstring nt_name;

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

	/*
	 * name details
	 */

	if (sam == NULL)
	{
		return NULL;
	}

	if (!found && sam->unix_name != NULL)
	{
		found = lookupsmbpwnam(sam->unix_name, &gmep);
	}
	if (!found && sam->unix_uid  != (uid_t)-1)
	{
		found = lookupsmbpwuid(sam->unix_uid , &gmep);
	}
	if (!found && sam->user_rid != 0xffffffff)
	{
		sid_copy(&sid, &global_sam_sid);
		sid_append_rid(&sid, sam->user_rid);
		found = lookupsmbpwsid  (&sid        , &gmep);
	}
	if (!found && sam->nt_name  != NULL)
	{
		found = lookupsmbpwntnam(sam->nt_name, &gmep);
	}

	if (!found)
	{
		return NULL;
	}

	if (!sid_front_equal(&global_sam_sid, &gmep.sid))
	{
		return NULL;
	}

	fstrcpy(unix_name, gmep.unix_name);
	fstrcpy(nt_name  , gmep.nt_name  );
	if (sam->unix_name == NULL      ) sam->unix_name = unix_name;
	if (sam->nt_name   == NULL      ) sam->nt_name   = nt_name  ;
	if (sam->unix_uid  == (uid_t)-1 ) sam->unix_uid  = (uid_t)gmep.unix_id;
	if (sam->user_rid  == 0xffffffff) sid_split_rid(&gmep.sid, &sam->user_rid);

	DEBUG(10,("pwdb_sam_map_name: found unix user %s nt %s uid %d rid 0x%x\n",
	           sam->unix_name, sam->nt_name, sam->unix_uid, sam->user_rid));

	/*
	 * group details
	 */

	found = False;

	if (sam->unix_gid != (gid_t)-1 && sam->group_rid != 0xffffffff)
	{
		return sam;
	}

	if (sam->unix_gid == (gid_t)-1 && sam->group_rid == 0xffffffff)
	{
		struct passwd *pass = getpwnam(unix_name);
		if (pass != NULL)
		{
			sam->unix_gid = pass->pw_gid;
		}
		else
		{
			DEBUG(0,("pwdb_sam_map_names: no unix password entry for %s\n",
			          unix_name));
		}
	}

	if (!found && sam->unix_gid  != (gid_t)-1)
	{
		found = lookupsmbgrpgid(sam->unix_gid , &gmep);
	}
	if (!found && sam->group_rid != 0xffffffff)
	{
		sid_copy(&sid, &global_sam_sid);
		sid_append_rid(&sid, sam->group_rid);
		found = lookupsmbgrpsid(&sid        , &gmep);
	}

	if (!found)
	{
		if (IS_BITS_SET_SOME(sam->acct_ctrl, ACB_WSTRUST|ACB_DOMTRUST|ACB_SVRTRUST))
		{
			if (!trust_account_warning_done)
			{
				trust_account_warning_done = True;
				DEBUG(0, ("\
pwdb_sam_map_names: your unix password database appears to have difficulties\n\
resolving trust account %s, probably because it ends in a '$'.\n\
you will get this warning only once (for all trust accounts)\n", unix_name));
			}
			/*
			 * oh, dear.
			 */
			if (sam->unix_gid != (gid_t)-1)
			{
				sam->unix_gid = (gid_t)-1;
			}
			sam->group_rid = DOMAIN_GROUP_RID_USERS;

			return sam;
		}
		else
		{
			DEBUG(0, ("pwdb_sam_map_names: could not find Primary Group for %s\n",
				   unix_name));
			return NULL;
		}
	}

	if (!sid_front_equal(&global_sam_sid, &gmep.sid))
	{
		return NULL;
	}

	if (sam->unix_gid  == (gid_t)-1 ) sam->unix_gid  = (gid_t)gmep.unix_id;
	if (sam->group_rid == 0xffffffff) sid_split_rid(&gmep.sid, &sam->group_rid);

	DEBUG(10,("pwdb_sam_map_name: found gid %d and group rid 0x%x for unix user %s\n",
	           sam->unix_gid, sam->group_rid, sam->unix_name));

	return sam;
}