import argparse
import binascii
import time
import random
import socket
import string
import sys
import uuid
import errno
try:
	import codecs
except ImportError:
	import upy.codecs as codecs

import filetimes, rpcBind, rpcRequest
from dcerpc import MSRPCHeader, MSRPCBindNak, MSRPCRespHeader, MSRPC_BINDACK, MSRPC_BINDNAK
from kmsBase import kmsRequestStruct, UUID
from kmsRequestV4 import kmsRequestV4, generateHash
from kmsRequestV5 import kmsRequestV5
from kmsRequestV6 import kmsRequestV6

config = {}

def main():
	parser = argparse.ArgumentParser()
	parser.add_argument("ip", action="store", help="The IP address or hostname of the KMS host.", type=str)
	parser.add_argument("port", nargs="?", action="store", default=1688, help="The port the KMS service is listening on. The default is \"1688\".", type=int)
	parser.add_argument("-m", "--mode", dest="mode", choices=["WindowsVista","Windows7","Windows8","Windows81","Windows10","Office2010","Office2013","Office2016"], default="Windows7")
	parser.add_argument("-c", "--cmid", dest="cmid", default=None, help="Use this flag to manually specify a CMID to use. If no CMID is specified, a random CMID will be generated.", type=str)
	parser.add_argument("-n", "--name", dest="machineName", default=None, help="Use this flag to manually specify an ASCII machineName to use. If no machineName is specified, a random machineName will be generated.", type=str)
	parser.add_argument("-v", "--verbose", dest="verbose", action="store_const", const=True, default=False, help="Use this flag to enable verbose output.")
	parser.add_argument("-d", "--debug", dest="debug", action="store_const", const=True, default=False, help="Use this flag to enable debug output. Implies \"-v\".")
	config.update(vars(parser.parse_args()))
	checkConfig()
	config['call_id'] = 1
	if config['debug']:
		config['verbose'] = True
	updateConfig()
	print("Connecting to %s on port %d..." % (config['ip'], config['port']))
	s = socket.create_connection((config['ip'], config['port']))
	if config['verbose']:
		print("Connection successful!")
	binder = rpcBind.handler(None, config)
	RPC_Bind = bytes(binder.generateRequest())
	if config['verbose']:
		print("Sending RPC bind request...")
	s.send(RPC_Bind)
	try:
		bindResponse = s.recv(1024)
	except socket.error as e:
		if e.errno == errno.ECONNRESET:
			print("Error: Connection reset by peer. Exiting...")
			sys.exit()
		else:
			raise
	if bindResponse == '' or not bindResponse:
		print("No data received! Exiting...")
		sys.exit()
	packetType = MSRPCHeader(bindResponse)['type']
	if packetType == MSRPC_BINDACK:
		if config['verbose']:
			print("RPC bind acknowledged.")
		kmsRequest = createKmsRequest()
		requester = rpcRequest.handler(kmsRequest, config)
		s.send(bytes(requester.generateRequest()))
		response = s.recv(1024)
		if config['debug']:
			print("Response:", binascii.b2a_hex(response))
		parsed = MSRPCRespHeader(response)
		kmsData = readKmsResponse(parsed['pduData'], kmsRequest, config)
		kmsResp = kmsData['response']
		try:
			hwid = kmsData['hwid']
			print("KMS Host HWID:", binascii.b2a_hex(hwid).upper())
		except KeyError:
			pass
		print("KMS Host ePID:", kmsResp['kmsEpid'])
		print("KMS Host Current Client Count:", kmsResp['currentClientCount'])
		print("KMS VL Activation Interval:", kmsResp['vLActivationInterval'])
		print("KMS VL Renewal Interval:", kmsResp['vLRenewalInterval'])
	elif packetType == MSRPC_BINDNAK:
		print(MSRPCBindNak(bindResponse).dump())
		sys.exit()
	else:
		print("Something went wrong.")
		sys.exit()

def checkConfig():
	if config['cmid'] is not None:
		try:
			uuid.UUID(config['cmid'])
		except ValueError:
			print("Error: Bad CMID. Exiting...")
			raise
	if config['machineName'] is not None:
		if len(config['machineName']) < 2 or len(config['machineName']) > 63:
			print("Error: machineName must be between 2 and 63 characters in length.")
			sys.exit()

def updateConfig():
	if config['mode'] == 'WindowsVista':
		config['RequiredClientCount'] = 25
		config['KMSProtocolMajorVersion'] = 4
		config['KMSProtocolMinorVersion'] = 0
		config['KMSClientLicenseStatus'] = 2
		config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f"
		config['KMSClientSkuID'] = "cfd8ff08-c0d7-452b-9f60-ef5c70c32094"
		config['KMSClientKMSCountedID'] = "212a64dc-43b1-4d3d-a30c-2fc69d2095c6"
	elif config['mode'] == 'Windows7':
		config['RequiredClientCount'] = 25
		config['KMSProtocolMajorVersion'] = 4
		config['KMSProtocolMinorVersion'] = 0
		config['KMSClientLicenseStatus'] = 2
		config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f"
		config['KMSClientSkuID'] = "ae2ee509-1b34-41c0-acb7-6d4650168915"
		config['KMSClientKMSCountedID'] = "7fde5219-fbfa-484a-82c9-34d1ad53e856"
	elif config['mode'] == 'Windows8':
		config['RequiredClientCount'] = 25
		config['KMSProtocolMajorVersion'] = 5
		config['KMSProtocolMinorVersion'] = 0
		config['KMSClientLicenseStatus'] = 2
		config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f"
		config['KMSClientSkuID'] = "458e1bec-837a-45f6-b9d5-925ed5d299de"
		config['KMSClientKMSCountedID'] = "3c40b358-5948-45af-923b-53d21fcc7e79"
	elif config['mode'] == 'Windows81':
		config['RequiredClientCount'] = 25
		config['KMSProtocolMajorVersion'] = 6
		config['KMSProtocolMinorVersion'] = 0
		config['KMSClientLicenseStatus'] = 2
		config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f"
		config['KMSClientSkuID'] = "81671aaf-79d1-4eb1-b004-8cbbe173afea"
		config['KMSClientKMSCountedID'] = "cb8fc780-2c05-495a-9710-85afffc904d7"
	elif config['mode'] == 'Windows10':
		config['RequiredClientCount'] = 25
		config['KMSProtocolMajorVersion'] = 6
		config['KMSProtocolMinorVersion'] = 0
		config['KMSClientLicenseStatus'] = 2
		config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f"
		config['KMSClientSkuID'] = "73111121-5638-40f6-bc11-f1d7b0d64300"
		config['KMSClientKMSCountedID'] = "58e2134f-8e11-4d17-9cb2-91069c151148"
	elif config['mode'] == 'Office2010':
		config['RequiredClientCount'] = 5
		config['KMSProtocolMajorVersion'] = 4
		config['KMSProtocolMinorVersion'] = 0
		config['KMSClientLicenseStatus'] = 2
		config['KMSClientAppID'] = "59a52881-a989-479d-af46-f275c6370663"
		config['KMSClientSkuID'] = "6f327760-8c5c-417c-9b61-836a98287e0c"
		config['KMSClientKMSCountedID'] = "e85af946-2e25-47b7-83e1-bebcebeac611"
	elif config['mode'] == 'Office2013':
		config['RequiredClientCount'] = 5
		config['KMSProtocolMajorVersion'] = 5
		config['KMSProtocolMinorVersion'] = 0
		config['KMSClientLicenseStatus'] = 2
		config['KMSClientAppID'] = "0ff1ce15-a989-479d-af46-f275c6370663"
		config['KMSClientSkuID'] = "b322da9c-a2e2-4058-9e4e-f59a6970bd69"
		config['KMSClientKMSCountedID'] = "e6a6f1bf-9d40-40c3-aa9f-c77ba21578c0"
	elif config['mode'] == 'Office2016':
		config['RequiredClientCount'] = 5
		config['KMSProtocolMajorVersion'] = 6
		config['KMSProtocolMinorVersion'] = 0
		config['KMSClientLicenseStatus'] = 2
		config['KMSClientAppID'] = "0ff1ce15-a989-479d-af46-f275c6370663"
		config['KMSClientSkuID'] = "d450596f-894d-49e0-966a-fd39ed4c4c64"
		config['KMSClientKMSCountedID'] = "85b5f61b-320b-4be3-814a-b76b2bfafc82"

def createKmsRequestBase():
	requestDict = kmsRequestStruct()
	requestDict['versionMinor'] = config['KMSProtocolMinorVersion']
	requestDict['versionMajor'] = config['KMSProtocolMajorVersion']
	requestDict['isClientVm'] = 0
	requestDict['licenseStatus'] = config['KMSClientLicenseStatus']
	requestDict['graceTime'] = 43200
	requestDict['applicationId'] = UUID(uuid.UUID(config['KMSClientAppID']).bytes_le)
	requestDict['skuId'] = UUID(uuid.UUID(config['KMSClientSkuID']).bytes_le)
	requestDict['kmsCountedId'] = UUID(uuid.UUID(config['KMSClientKMSCountedID']).bytes_le)
	requestDict['clientMachineId'] = UUID(uuid.UUID(config['cmid']).bytes_le if (config['cmid'] is not None) else uuid.uuid4().bytes_le)
	requestDict['previousClientMachineId'] = b'\0' * 16 #requestDict['clientMachineId'] # I'm pretty sure this is supposed to be a null UUID.
	requestDict['requiredClientCount'] = config['RequiredClientCount']
	requestDict['requestTime'] = filetimes.timestamp2filetime(time.time())
	requestDict['machineName'] = codecs.encode(config['machineName'] or ''.join(random.choice(string.ascii_letters + string.digits) for i in range(random.randint(2,63))), 'utf_16_le')
	requestDict['mnPad'] = codecs.encode('\0', 'utf_16_le') * (63 - len(codecs.decode(requestDict['machineName'], 'utf_16_le')))

	# Debug Stuff
	if config['debug']:
		print("Request Base Dictionary:", requestDict.dump())

	return requestDict

def createKmsRequest():
	# Update the call ID
	config['call_id'] += 1

	# KMS Protocol Major Version
	if config['KMSProtocolMajorVersion'] == 4:
		handler = kmsRequestV4(None, config)
	elif config['KMSProtocolMajorVersion'] == 5:
		handler = kmsRequestV5(None, config)
	elif config['KMSProtocolMajorVersion'] == 6:
		handler = kmsRequestV6(None, config)
	else:
		return None

	requestBase = createKmsRequestBase()
	return handler.generateRequest(requestBase)

def readKmsResponse(data, request, config):
	if config['KMSProtocolMajorVersion'] == 4:
		print("Received V4 response")
		response = readKmsResponseV4(data)
	elif config['KMSProtocolMajorVersion'] == 5:
		print("Received V5 response")
		response = readKmsResponseV5(data)
	elif config['KMSProtocolMajorVersion'] == 6:
		print("Received V6 response")
		response = readKmsResponseV6(data)
	else:
		print("Unhandled response version: %d.%d" % (config['KMSProtocolMajorVersion'], config['KMSProtocolMinorVersion']))
		print("I'm not even sure how this happened...")
	return response

def readKmsResponseV4(data):
	response = kmsRequestV4.ResponseV4(data)
	hashed = generateHash(bytearray(bytes(response['response'])))
	print("Response Hash has expected value:", hashed == response['hash'])
	return response

def readKmsResponseV5(data):
	response = kmsRequestV5.ResponseV5(data)
	decrypted = kmsRequestV5(data, config).decryptResponse(response)
	return decrypted

def readKmsResponseV6(data):
	response = kmsRequestV6.ResponseV5(data)
	decrypted = kmsRequestV6(data, config).decryptResponse(response)
	message = decrypted['message']
	return message

if __name__ == "__main__":
	main()
