#
# This code is copied from: https://gist.github.com/mkropat/7559409
# Author: mkropat
# Liense: MIT
#



import os

class UserNotFound(Exception): pass

### Windows ###
if os.name == 'nt':
	import ctypes
	from ctypes import windll, wintypes

	class WinError:	 # [1]
		InvalidParameter = 87
		InvalidSid = 1337
	#

	class NtStatus:	 # [2]
		Success		 = 0x00000000
		NoneMapped	  = 0xC0000073
	#

	class AccessMask:   # [3]
		Delete					  = 0x00010000
		ReadControl				 = 0x00020000
		WriteDAC					= 0x00040000
		WriteOwner				  = 0x00080000
		Synchronize				 = 0x00100000
		StandardRightsRequired	  = 0x000F0000
		StandardRightsRead		  = ReadControl
		StandardRightsWrite		 = ReadControl
		StandardRightsExecute	   = ReadControl
		StandardRightsAll		   = 0x001F0000
		SpecificRightsAll		   = 0x0000FFFF
	#

	class TokenAccessLevels:		# See [4] and TokenAccessLevels.cs in Mono source
		AssignPrimary	   = 1<<0
		Duplicate		   = 1<<1
		Impersonate		 = 1<<2
		Query			   = 1<<3
		QuerySource		 = 1<<4
		AdjustPrivileges	= 1<<5
		AdjustGroups		= 1<<6
		AdjustDefault	   = 1<<7
		AdjustSessionId	 = 1<<8
		Read				= AccessMask.StandardRightsRead | Query
		Write			   = AccessMask.StandardRightsWrite | AdjustPrivileges | AdjustGroups | AdjustDefault
		AllAccess		   = AccessMask.StandardRightsRequired | 0x1FF
		MaximumAllowed	  = 0x2000000
	#

	class TokenInformationClass:	# [5]
		User,\
		Groups,\
		Privileges,\
		Owner,\
		PrimaryGroup,\
		DefaultDacl,\
		Source,\
		Type,\
		ImpersonationLevel,\
		Statistics,\
		RestrictedSids,\
		SessionId,\
		GroupsAndPrivileges,\
		SessionReference,\
		SandBoxInert,\
		AuditPolicy,\
		Origin,\
		ElevationType,\
		LinkedToken,\
		Elevation,\
		HasRestrictions,\
		AccessInformation,\
		VirtualizationAllowed,\
		VirtualizationEnabled,\
		IntegrityLevel,\
		UIAccess,\
		MandatoryPolicy,\
		LogonSid,\
		IsAppContainer,\
		Capabilities,\
		AppContainerSid,\
		AppContainerNumber,\
		UserClaimAttributes,\
		DeviceClaimAttributes,\
		RestrictedUserClaimAttributes,\
		RestrictedDeviceClaimAttributes,\
		DeviceGroups,\
		RestrictedDeviceGroups,\
		SecurityAttributes,\
		IsRestricted,\
		= range(1, 41)
	#

	class SidNameUse:   # [6]
		TypeUser,\
		TypeGroup,\
		TypeDomain,\
		TypeAlias,\
		TypeWellKnownGroup,\
		TypeDeletedAccount,\
		TypeInvalid,\
		TypeUnknown,\
		TypeComputer,\
		TypeLabel,\
		= range(1, 11)
	#

	# Type declarations
	pSID = wintypes.HANDLE
	ENUM = ctypes.c_uint

	class SidAndAttributes(ctypes.Structure):   # [7]
		_fields_ = (
			('Sid', pSID),
			('Attributes', wintypes.DWORD),
		)
	#

	class TokenUser(ctypes.Structure):		  # [8]
		_fields_ = (
			('User', SidAndAttributes),
		)
	#

	_GetCurrentProcess = windll.kernel32.GetCurrentProcess	  # [9]
	_GetCurrentProcess.restype = wintypes.HANDLE

	_OpenProcessToken = windll.advapi32.OpenProcessToken		# [10]
	_OpenProcessToken.restype = wintypes.BOOL
	_OpenProcessToken.argtypes = (
		wintypes.HANDLE,				# ProcessHandle
		wintypes.DWORD,				 # DesiredAccess
		ctypes.POINTER(wintypes.HANDLE) # TokenHandle (out)
	)

	_GetTokenInformation = windll.advapi32.GetTokenInformation  # [11]
	_GetTokenInformation.restype = wintypes.BOOL
	_GetTokenInformation.argtypes = (
		wintypes.HANDLE,	# TokenHandle
		ENUM,			   # TokenInformationClass
		ctypes.c_void_p,	# TokenInformation (out)
		wintypes.DWORD,	 # TokenInformationLength
		wintypes.PDWORD	 # ReturnLength (out)
	)

	_ConvertStringSidToSid = windll.advapi32.ConvertStringSidToSidW	 # [12]
	_ConvertStringSidToSid.restype = wintypes.BOOL
	_ConvertStringSidToSid.argtypes = (
		wintypes.LPWSTR,		# StringSid
		ctypes.POINTER(pSID),   # Sid (out)
	)

	_CovertSidToStringSid = windll.advapi32.ConvertSidToStringSidW	  # [13]
	_CovertSidToStringSid.restype = wintypes.BOOL
	_CovertSidToStringSid.argtypes = (
		pSID,						   # Sid
		ctypes.POINTER(wintypes.LPWSTR) # StringSid (out)
	)

	_LocalFree = windll.kernel32.LocalFree	  # [14]
	_LocalFree.restype = None
	_LocalFree.argtypes = (wintypes.HANDLE,)

	_IsValidSid = windll.advapi32.IsValidSid	# [15]
	_IsValidSid.restype = wintypes.BOOL
	_IsValidSid.argtypes = (pSID,)

	_LookupAccountSid = windll.advapi32.LookupAccountSidW   # [16]
	_LookupAccountSid.restype = wintypes.BOOL
	_LookupAccountSid.argtypes = (
		wintypes.LPWSTR,		# lpSystemName (optional)
		pSID,				   # lpSid
		wintypes.LPWSTR,		# lpName (out, optional)
		wintypes.LPDWORD,	   # cchName (in/out)
		wintypes.LPWSTR,		# lpReferencedDomainName (out, optional)
		wintypes.LPDWORD,	   # cchReferencedDomainName (in/out)
		ctypes.POINTER(ENUM),   # peUse (out)
	)

	class Sid:
		def __init__(self, sid):
			if isinstance(sid, str):
				self.pointer = pSID()
				status = _ConvertStringSidToSid(sid, self.pointer)
				if status == WinError.InvalidSid:
					raise ValueError('Not a valid sid: %s' % sid)
				elif not status:
					raise ctypes.WinError()
			else:
				if not _IsValidSid(sid):
					raise ValueError('Invalid sid: %s' % uid)
				self.pointer = sid

		def __str__(self):
			buf = wintypes.LPWSTR()
			if not _CovertSidToStringSid(self.pointer, buf):
				raise ctypes.WinError()
			string_sid = buf.value
			_LocalFree(buf)
			return string_sid
	#

	def _getpsid():
		proc_token = wintypes.HANDLE()
		if not _OpenProcessToken(_GetCurrentProcess(), TokenAccessLevels.Read, proc_token):
			raise ctypes.WinError()

		# Get required length by calling with a 0 length buffer
		length = wintypes.DWORD()
		_GetTokenInformation(proc_token, TokenInformationClass.User, None, 0, length)

		buf = ctypes.create_string_buffer(length.value)
		if not _GetTokenInformation(proc_token, TokenInformationClass.User, buf, len(buf), length):
			raise ctypes.WinError()

		token_user = ctypes.cast(buf, ctypes.POINTER(TokenUser)).contents
		return token_user.User.Sid
	#


	def getuid() -> int:
		return str(Sid(_getpsid()))
	#

	def lookup_username(uid=None) -> str:
		sid = Sid(uid or _getpsid())

		use = ENUM()
		cchName = wintypes.DWORD()
		cchReferencedDomainName = wintypes.DWORD()

		# Get required lengths by calling with 0 length buffers
		_LookupAccountSid(None, sid.pointer, None, cchName, None, cchReferencedDomainName, use)

		name = ctypes.create_unicode_buffer(cchName.value)
		domain = ctypes.create_unicode_buffer(cchReferencedDomainName.value)

		status = _LookupAccountSid(None, sid.pointer, name, cchName, domain, cchReferencedDomainName, use)
		if status == NtStatus.NoneMapped:
			raise UserNotFound('No such sid: %s' % sid)
		elif not status:
			raise ctypes.WinError()

		if use.value != SidNameUse.TypeUser:
			raise ValueError('Not a user sid: %s' % sid)

		return name.value
	#

### Posix ###
else:
	import pwd
	from os import getuid

	def lookup_username(uid=None) -> str:
		if uid is None:
			uid = getuid()
		try:
			return pwd.getpwuid(uid)[0]
		except (KeyError, TypeError):
			raise UserNotFound('No such uid: %s' % uid)
	#



def getUserHome() -> str:
	return os.path.expanduser("~")
#










# References:
# [1] http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381
# [2] http://msdn.microsoft.com/en-us/library/cc704588
# [3] http://msdn.microsoft.com/en-us/library/windows/desktop/aa374892
# [4] http://msdn.microsoft.com/en-us/library/aa374905
# [5] http://msdn.microsoft.com/en-us/library/windows/desktop/aa379626
# [6] http://msdn.microsoft.com/en-us/library/windows/desktop/aa379601
# [7] http://msdn.microsoft.com/en-us/library/windows/desktop/aa379595
# [8] http://msdn.microsoft.com/en-us/library/windows/desktop/aa379634
# [9] http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179
# [10] http://msdn.microsoft.com/en-us/library/aa379295
# [11] http://msdn.microsoft.com/en-us/library/windows/desktop/aa446671
# [12] http://msdn.microsoft.com/en-us/library/aa376402
# [13] http://msdn.microsoft.com/en-us/library/windows/desktop/aa376399
# [14] http://msdn.microsoft.com/en-us/library/windows/desktop/aa366730
# [15] http://msdn.microsoft.com/en-us/library/windows/desktop/aa379151
# [16] http://msdn.microsoft.com/library/windows/desktop/aa379166



