/* uinfo.cc: user info (uid, gid, etc...)

   Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
   2006, 2007, 2008 Red Hat, Inc.

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include "winsup.h"
#include <unistd.h>
#include <wininet.h>
#include <stdlib.h>
#include <wchar.h>
#include <lm.h>
#include <iptypes.h>
#include <sys/cygwin.h>
#include "cygerrno.h"
#include "pinfo.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "shared_info.h"
#include "registry.h"
#include "child_info.h"
#include "environ.h"
#include "pwdgrp.h"
#include "tls_pbuf.h"
#include "ntdll.h"

/* Initialize the part of cygheap_user that does not depend on files.
   The information is used in shared.cc for the user shared.
   Final initialization occurs in uinfo_init */
void
cygheap_user::init ()
{
  WCHAR user_name[UNLEN + 1];
  DWORD user_name_len = UNLEN + 1;

  if (!GetUserNameW (user_name, &user_name_len))
    wcpcpy (user_name, L"unknown");

  char mb_user_name[user_name_len = sys_wcstombs (NULL, 0, user_name)];
  sys_wcstombs (mb_user_name, user_name_len, user_name);
  set_name (mb_user_name);

  DWORD siz;
  PSECURITY_DESCRIPTOR psd;

  if (!GetTokenInformation (hProcToken, TokenPrimaryGroup,
			    &groups.pgsid, sizeof (cygsid), &siz))
    system_printf ("GetTokenInformation (TokenPrimaryGroup), %E");

  /* Get the SID from current process and store it in effec_cygsid */
  if (!GetTokenInformation (hProcToken, TokenUser, &effec_cygsid,
			    sizeof (cygsid), &siz))
    {
      system_printf ("GetTokenInformation (TokenUser), %E");
      return;
    }

  /* Set token owner to the same value as token user */
  if (!SetTokenInformation (hProcToken, TokenOwner, &effec_cygsid,
			    sizeof (cygsid)))
    debug_printf ("SetTokenInformation(TokenOwner), %E");

  /* Standard way to build a security descriptor with the usual DACL */
  PSECURITY_ATTRIBUTES sa_buf = (PSECURITY_ATTRIBUTES) alloca (1024);
  psd = (PSECURITY_DESCRIPTOR)
  		(sec_user_nih (sa_buf, sid()))->lpSecurityDescriptor;

  BOOL acl_exists, dummy;
  TOKEN_DEFAULT_DACL dacl;
  if (GetSecurityDescriptorDacl (psd, &acl_exists, &dacl.DefaultDacl, &dummy)
      && acl_exists && dacl.DefaultDacl)
    {
      NTSTATUS status;

      /* Set the default DACL and the process DACL */
      if (!SetTokenInformation (hProcToken, TokenDefaultDacl, &dacl,
      				sizeof (dacl)))
	system_printf ("SetTokenInformation (TokenDefaultDacl), %E");
      if ((status = NtSetSecurityObject (NtCurrentProcess (),
					 DACL_SECURITY_INFORMATION, psd)))
	system_printf ("NtSetSecurityObject, %lx", status);
    }
  else
    system_printf("Cannot get dacl, %E");
}

void
internal_getlogin (cygheap_user &user)
{
  struct passwd *pw = NULL;

  cygpsid psid = user.sid ();
  pw = internal_getpwsid (psid);

  if (!pw && !(pw = internal_getpwnam (user.name ()))
      && !(pw = internal_getpwuid (DEFAULT_UID)))
    debug_printf ("user not found in augmented /etc/passwd");
  else
    {
      cygsid gsid;

      myself->uid = pw->pw_uid;
      myself->gid = pw->pw_gid;
      user.set_name (pw->pw_name);
      if (gsid.getfromgr (internal_getgrgid (pw->pw_gid)))
	{
	  if (gsid != user.groups.pgsid)
	    {
	      /* Set primary group to the group in /etc/passwd. */
	      if (!SetTokenInformation (hProcToken, TokenPrimaryGroup,
					&gsid, sizeof gsid))
		debug_printf ("SetTokenInformation(TokenPrimaryGroup), %E");
	      else
		user.groups.pgsid = gsid;
	      clear_procimptoken ();
	    }
	}
      else
	debug_printf ("gsid not found in augmented /etc/group");
    }
  cygheap->user.ontherange (CH_HOME, pw);
}

void
uinfo_init ()
{
  if (child_proc_info && !cygheap->user.has_impersonation_tokens ())
    return;

  if (!child_proc_info)
    internal_getlogin (cygheap->user); /* Set the cygheap->user. */
  /* Conditions must match those in spawn to allow starting child
     processes with ruid != euid and rgid != egid. */
  else if (cygheap->user.issetuid ()
	   && cygheap->user.saved_uid == cygheap->user.real_uid
	   && cygheap->user.saved_gid == cygheap->user.real_gid
	   && !cygheap->user.groups.issetgroups ()
	   && !cygheap->user.setuid_to_restricted)
    {
      cygheap->user.reimpersonate ();
      return;
    }
  else
    cygheap->user.close_impersonation_tokens ();

  cygheap->user.saved_uid = cygheap->user.real_uid = myself->uid;
  cygheap->user.saved_gid = cygheap->user.real_gid = myself->gid;
  cygheap->user.external_token = NO_IMPERSONATION;
  cygheap->user.internal_token = NO_IMPERSONATION;
  cygheap->user.curr_primary_token = NO_IMPERSONATION;
  cygheap->user.curr_imp_token = NO_IMPERSONATION;
  cygheap->user.ext_token_is_restricted = false;
  cygheap->user.curr_token_is_restricted = false;
  cygheap->user.setuid_to_restricted = false;
  cygheap->user.set_saved_sid ();	/* Update the original sid */
  cygheap->user.deimpersonate ();
}

extern "C" int
getlogin_r (char *name, size_t namesize)
{
  char *login = getlogin ();
  size_t len = strlen (login) + 1;
  if (len > namesize)
    return ERANGE;
  myfault efault;
  if (efault.faulted ())
    return EFAULT;
  strncpy (name, login, len);
  return 0;
}

extern "C" char *
getlogin (void)
{
  return strcpy (_my_tls.locals.username, cygheap->user.name ());
}

extern "C" __uid32_t
getuid32 (void)
{
  return cygheap->user.real_uid;
}

extern "C" __uid16_t
getuid (void)
{
  return cygheap->user.real_uid;
}

extern "C" __gid32_t
getgid32 (void)
{
  return cygheap->user.real_gid;
}

extern "C" __gid16_t
getgid (void)
{
  return cygheap->user.real_gid;
}

extern "C" __uid32_t
geteuid32 (void)
{
  return myself->uid;
}

extern "C" __uid16_t
geteuid (void)
{
  return myself->uid;
}

extern "C" __gid32_t
getegid32 (void)
{
  return myself->gid;
}

extern "C" __gid16_t
getegid (void)
{
  return myself->gid;
}

/* Not quite right - cuserid can change, getlogin can't */
extern "C" char *
cuserid (char *src)
{
  if (!src)
    return getlogin ();

  strcpy (src, getlogin ());
  return src;
}

const char *
cygheap_user::ontherange (homebodies what, struct passwd *pw)
{
  LPUSER_INFO_3 ui = NULL;
  WCHAR wuser[UNLEN + 1];
  NET_API_STATUS ret;
  char homedrive_env_buf[3];
  char *newhomedrive = NULL;
  char *newhomepath = NULL;
  tmp_pathbuf tp;

  debug_printf ("what %d, pw %p", what, pw);
  if (what == CH_HOME)
    {
      char *p;

      if ((p = getenv ("HOME")))
	debug_printf ("HOME is already in the environment %s", p);
      else
	{
	  if (pw && pw->pw_dir && *pw->pw_dir)
	    {
	      debug_printf ("Set HOME (from /etc/passwd) to %s", pw->pw_dir);
	      setenv ("HOME", pw->pw_dir, 1);
	    }
	  else
	    {
	      char home[strlen (name ()) + 8];

	      debug_printf ("Set HOME to default /home/USER");
	      __small_sprintf (home, "/home/%s", name ());
	      setenv ("HOME", home, 1);
	    }
	}
    }

  if (what != CH_HOME && homepath == NULL && newhomepath == NULL)
    {
      char *homepath_env_buf = tp.c_get ();
      if (!pw)
	pw = internal_getpwnam (name ());
      if (pw && pw->pw_dir && *pw->pw_dir)
	cygwin_conv_path (CCP_POSIX_TO_WIN_A, pw->pw_dir, homepath_env_buf,
			  NT_MAX_PATH);
      else
	{
	  homepath_env_buf[0] = homepath_env_buf[1] = '\0';
	  if (logsrv ())
	    {
	      WCHAR wlogsrv[INTERNET_MAX_HOST_NAME_LENGTH + 3];
	      sys_mbstowcs (wlogsrv, sizeof (wlogsrv) / sizeof (*wlogsrv),
			    logsrv ());
	     sys_mbstowcs (wuser, sizeof (wuser) / sizeof (*wuser), winname ());
	      if (!(ret = NetUserGetInfo (wlogsrv, wuser, 3, (LPBYTE *) &ui)))
		{
		  sys_wcstombs (homepath_env_buf, NT_MAX_PATH,
		  		ui->usri3_home_dir);
		  if (!homepath_env_buf[0])
		    {
		      sys_wcstombs (homepath_env_buf, NT_MAX_PATH,
				    ui->usri3_home_dir_drive);
		      if (homepath_env_buf[0])
			strcat (homepath_env_buf, "\\");
		      else
			cygwin_conv_path (CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE,
					  "/", homepath_env_buf, NT_MAX_PATH);
		    }
		}
	    }
	  if (ui)
	    NetApiBufferFree (ui);
	}

      if (homepath_env_buf[1] != ':')
	{
	  newhomedrive = almost_null;
	  newhomepath = homepath_env_buf;
	}
      else
	{
	  homedrive_env_buf[0] = homepath_env_buf[0];
	  homedrive_env_buf[1] = homepath_env_buf[1];
	  homedrive_env_buf[2] = '\0';
	  newhomedrive = homedrive_env_buf;
	  newhomepath = homepath_env_buf + 2;
	}
    }

  if (newhomedrive && newhomedrive != homedrive)
    cfree_and_set (homedrive, (newhomedrive == almost_null)
			      ? almost_null : cstrdup (newhomedrive));

  if (newhomepath && newhomepath != homepath)
    cfree_and_set (homepath, cstrdup (newhomepath));

  switch (what)
    {
    case CH_HOMEDRIVE:
      return homedrive;
    case CH_HOMEPATH:
      return homepath;
    default:
      return homepath;
    }
}

const char *
cygheap_user::test_uid (char *&what, const char *name, size_t namelen)
{
  if (!what && !issetuid ())
    what = getwinenveq (name, namelen, HEAP_STR);
  return what;
}

const char *
cygheap_user::env_logsrv (const char *name, size_t namelen)
{
  if (test_uid (plogsrv, name, namelen))
    return plogsrv;

  const char *mydomain = domain ();
  const char *myname = winname ();
  if (!mydomain || ascii_strcasematch (myname, "SYSTEM"))
    return almost_null;

  WCHAR wdomain[MAX_DOMAIN_NAME_LEN + 1];
  WCHAR wlogsrv[INTERNET_MAX_HOST_NAME_LENGTH + 3];
  sys_mbstowcs (wdomain, MAX_DOMAIN_NAME_LEN + 1, mydomain);
  cfree_and_set (plogsrv, almost_null);
  if (get_logon_server (wdomain, wlogsrv, false))
    sys_wcstombs_alloc (&plogsrv, HEAP_STR, wlogsrv);
  return plogsrv;
}

const char *
cygheap_user::env_domain (const char *name, size_t namelen)
{
  if (pwinname && test_uid (pdomain, name, namelen))
    return pdomain;

  char username[UNLEN + 1];
  DWORD ulen = sizeof (username);
  char userdomain[DNLEN + 1];
  DWORD dlen = sizeof (userdomain);
  SID_NAME_USE use;

  cfree_and_set (pwinname, almost_null);
  cfree_and_set (pdomain, almost_null);
  if (!LookupAccountSid (NULL, sid (), username, &ulen,
			 userdomain, &dlen, &use))
    __seterrno ();
  else
    {
      pwinname = cstrdup (username);
      pdomain = cstrdup (userdomain);
    }
  return pdomain;
}

const char *
cygheap_user::env_userprofile (const char *name, size_t namelen)
{
  if (test_uid (puserprof, name, namelen))
    return puserprof;

  WCHAR userprofile_env_buf[NT_MAX_PATH];
  WCHAR win_id[UNLEN + 1]; /* Large enough for SID */

  cfree_and_set (puserprof, almost_null);
  if (get_registry_hive_path (get_windows_id (win_id), userprofile_env_buf))
    sys_wcstombs_alloc (&puserprof, HEAP_STR, userprofile_env_buf);

  return puserprof;
}

const char *
cygheap_user::env_homepath (const char *name, size_t namelen)
{
  return ontherange (CH_HOMEPATH);
}

const char *
cygheap_user::env_homedrive (const char *name, size_t namelen)
{
  return ontherange (CH_HOMEDRIVE);
}

const char *
cygheap_user::env_name (const char *name, size_t namelen)
{
  if (!test_uid (pwinname, name, namelen))
    domain ();
  return pwinname;
}

const char *
cygheap_user::env_systemroot (const char *name, size_t namelen)
{
  if (!psystemroot)
    {
      int size = GetWindowsDirectory (NULL, 0);
      if (size > 0)
	{
	  psystemroot = (char *) cmalloc_abort (HEAP_STR, ++size);
	  size = GetWindowsDirectory (psystemroot, size);
	  if (size <= 0)
	    {
	      cfree (psystemroot);
	      psystemroot = NULL;
	    }
	}
      if (size <= 0)
	debug_printf ("GetWindowsDirectory(), %E");
    }
  return psystemroot;
}

char *
pwdgrp::next_str (char c)
{
  char *res = lptr;
  lptr = strechr (lptr, c);
  if (*lptr)
    *lptr++ = '\0';
  return res;
}

bool
pwdgrp::next_num (unsigned long& n)
{
  char *p = next_str (':');
  char *cp;
  n = strtoul (p, &cp, 10);
  return p != cp && !*cp;
}

char *
pwdgrp::add_line (char *eptr)
{
  if (eptr)
    {
      lptr = eptr;
      eptr = strchr (lptr, '\n');
      if (eptr)
	{
	  if (eptr > lptr && eptr[-1] == '\r')
	    eptr[-1] = '\0';
	  else
	    *eptr = '\0';
	  eptr++;
	}
      if (curr_lines >= max_lines)
	{
	  max_lines += 10;
	  *pwdgrp_buf = realloc (*pwdgrp_buf, max_lines * pwdgrp_buf_elem_size);
	}
      if ((this->*parse) ())
	curr_lines++;
    }
  return eptr;
}

void
pwdgrp::load (const wchar_t *rel_path)
{
  static const char failed[] = "failed";
  static const char succeeded[] = "succeeded";
  const char *res = failed;
  HANDLE fh = NULL;

  NTSTATUS status;
  OBJECT_ATTRIBUTES attr;
  IO_STATUS_BLOCK io;
  FILE_STANDARD_INFORMATION fsi;

  if (buf)
    free (buf);
  buf = NULL;
  curr_lines = 0;

  if (!path &&
      !(path = (PWCHAR) malloc ((wcslen (installation_root)
				 + wcslen (rel_path) + 1) * sizeof (WCHAR))))
    {
      paranoid_printf ("malloc (%W) failed", rel_path);
      goto out;
    }
  wcpcpy (wcpcpy (path, installation_root), rel_path);
  RtlInitUnicodeString (&upath, path);

  InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE, NULL, NULL);
  etc_ix = etc::init (etc_ix, &attr);

  paranoid_printf ("%S", &upath);

  status = NtOpenFile (&fh, SYNCHRONIZE | FILE_READ_DATA, &attr, &io,
		       FILE_SHARE_VALID_FLAGS,
		       FILE_SYNCHRONOUS_IO_NONALERT
		       | FILE_OPEN_FOR_BACKUP_INTENT);
  if (!NT_SUCCESS (status))
    {
      paranoid_printf ("NtOpenFile(%S) failed, status %p", &upath, status);
      goto out;
    }
  status = NtQueryInformationFile (fh, &io, &fsi, sizeof fsi,
				   FileStandardInformation);
  if (!NT_SUCCESS (status))
    {
      paranoid_printf ("NtQueryInformationFile(%S) failed, status %p",
		       &upath, status);
      goto out;
    }
  /* FIXME: Should we test for HighPart set?  If so, the
     passwd or group file is way beyond what we can handle. */
  /* FIXME 2: It's still ugly that we keep the file in memory.
     Big organizations have naturally large passwd files. */
  buf = (char *) malloc (fsi.EndOfFile.LowPart + 1);
  if (!buf)
    {
      paranoid_printf ("malloc (%d) failed", fsi.EndOfFile.LowPart);
      goto out;
    }
  status = NtReadFile (fh, NULL, NULL, NULL, &io, buf, fsi.EndOfFile.LowPart,
		       NULL, NULL);
  if (!NT_SUCCESS (status))
    {
      paranoid_printf ("NtReadFile(%S) failed, status %p", &upath, status);
      free (buf);
      goto out;
    }
  buf[fsi.EndOfFile.LowPart] = '\0';
  for (char *eptr = buf; (eptr = add_line (eptr)); )
    continue;
  debug_printf ("%W curr_lines %d", rel_path, curr_lines);
  res = succeeded;

out:
  if (fh)
    NtClose (fh);
  debug_printf ("%W load %s", rel_path, res);
  initialized = true;
}
