package credentials

import (
	
	
	
	
	
	
	

	
	
)

const (
	headerFieldTagKDCOffset = 1
)

// CCache is the file credentials cache as define here: https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html
type CCache struct {
	Version          uint8
	Header           header
	DefaultPrincipal principal
	Credentials      []*Credential
	Path             string
}

type header struct {
	length uint16
	fields []headerField
}

type headerField struct {
	tag    uint16
	length uint16
	value  []byte
}

// Credential cache entry principal struct.
type principal struct {
	Realm         string
	PrincipalName types.PrincipalName
}

// Credential holds a Kerberos client's ccache credential information.
type Credential struct {
	Client       principal
	Server       principal
	Key          types.EncryptionKey
	AuthTime     time.Time
	StartTime    time.Time
	EndTime      time.Time
	RenewTill    time.Time
	IsSKey       bool
	TicketFlags  asn1.BitString
	Addresses    []types.HostAddress
	AuthData     []types.AuthorizationDataEntry
	Ticket       []byte
	SecondTicket []byte
}

// LoadCCache loads a credential cache file into a CCache type.
func ( string) (*CCache, error) {
	 := new(CCache)
	,  := os.ReadFile()
	if  != nil {
		return , 
	}
	 = .Unmarshal()
	return , 
}

// Unmarshal a byte slice of credential cache data into CCache type.
func ( *CCache) ( []byte) error {
	 := 0
	//The first byte of the file always has the value 5
	if int8([]) != 5 {
		return errors.New("Invalid credential cache data. First byte does not equal 5")
	}
	++
	//Get credential cache version
	//The second byte contains the version number (1 to 4)
	.Version = []
	if .Version < 1 || .Version > 4 {
		return errors.New("Invalid credential cache data. Keytab version is not within 1 to 4")
	}
	++
	//Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order
	var  binary.ByteOrder
	 = binary.BigEndian
	if (.Version == 1 || .Version == 2) && isNativeEndianLittle() {
		 = binary.LittleEndian
	}
	if .Version == 4 {
		 := parseHeader(, &, , &)
		if  != nil {
			return 
		}
	}
	.DefaultPrincipal = parsePrincipal(, &, , &)
	for  < len() {
		,  := parseCredential(, &, , &)
		if  != nil {
			return 
		}
		.Credentials = append(.Credentials, )
	}
	return nil
}

func parseHeader( []byte,  *int,  *CCache,  *binary.ByteOrder) error {
	if .Version != 4 {
		return errors.New("Credentials cache version is not 4 so there is no header to parse.")
	}
	 := header{}
	.length = uint16(readInt16(, , ))
	for * <= int(.length) {
		 := headerField{}
		.tag = uint16(readInt16(, , ))
		.length = uint16(readInt16(, , ))
		.value = [* : *+int(.length)]
		* += int(.length)
		if !.valid() {
			return errors.New("Invalid credential cache header found")
		}
		.fields = append(.fields, )
	}
	.Header = 
	return nil
}

// Parse the Keytab bytes of a principal into a Keytab entry's principal.
func parsePrincipal( []byte,  *int,  *CCache,  *binary.ByteOrder) ( principal) {
	if .Version != 1 {
		//Name Type is omitted in version 1
		.PrincipalName.NameType = readInt32(, , )
	}
	 := int(readInt32(, , ))
	if .Version == 1 {
		//In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
		--
	}
	 := readInt32(, , )
	.Realm = string(readBytes(, , int(), ))
	for  := 0;  < ; ++ {
		 := readInt32(, , )
		.PrincipalName.NameString = append(.PrincipalName.NameString, string(readBytes(, , int(), )))
	}
	return 
}

func parseCredential( []byte,  *int,  *CCache,  *binary.ByteOrder) ( *Credential,  error) {
	 = new(Credential)
	.Client = parsePrincipal(, , , )
	.Server = parsePrincipal(, , , )
	 := types.EncryptionKey{}
	.KeyType = int32(readInt16(, , ))
	if .Version == 3 {
		//repeated twice in version 3
		.KeyType = int32(readInt16(, , ))
	}
	.KeyValue = readData(, , )
	.Key = 
	.AuthTime = readTimestamp(, , )
	.StartTime = readTimestamp(, , )
	.EndTime = readTimestamp(, , )
	.RenewTill = readTimestamp(, , )
	if  := readInt8(, , );  == 0 {
		.IsSKey = false
	} else {
		.IsSKey = true
	}
	.TicketFlags = types.NewKrbFlags()
	.TicketFlags.Bytes = readBytes(, , 4, )
	 := int(readInt32(, , ))
	.Addresses = make([]types.HostAddress, , )
	for  := range .Addresses {
		.Addresses[] = readAddress(, , )
	}
	 = int(readInt32(, , ))
	.AuthData = make([]types.AuthorizationDataEntry, , )
	for  := range .AuthData {
		.AuthData[] = readAuthDataEntry(, , )
	}
	.Ticket = readData(, , )
	.SecondTicket = readData(, , )
	return
}

// GetClientPrincipalName returns a PrincipalName type for the client the credentials cache is for.
func ( *CCache) () types.PrincipalName {
	return .DefaultPrincipal.PrincipalName
}

// GetClientRealm returns the reals of the client the credentials cache is for.
func ( *CCache) () string {
	return .DefaultPrincipal.Realm
}

// GetClientCredentials returns a Credentials object representing the client of the credentials cache.
func ( *CCache) () *Credentials {
	return &Credentials{
		username: .DefaultPrincipal.PrincipalName.PrincipalNameString(),
		realm:    .GetClientRealm(),
		cname:    .DefaultPrincipal.PrincipalName,
	}
}

// Contains tests if the cache contains a credential for the provided server PrincipalName
func ( *CCache) ( types.PrincipalName) bool {
	for ,  := range .Credentials {
		if .Server.PrincipalName.Equal() {
			return true
		}
	}
	return false
}

// GetEntry returns a specific credential for the PrincipalName provided.
func ( *CCache) ( types.PrincipalName) (*Credential, bool) {
	 := new(Credential)
	var  bool
	for  := range .Credentials {
		if .Credentials[].Server.PrincipalName.Equal() {
			 = .Credentials[]
			 = true
			break
		}
	}
	if ! {
		return , false
	}
	return , true
}

// GetEntries filters out configuration entries an returns a slice of credentials.
func ( *CCache) () []*Credential {
	 := make([]*Credential, 0)
	for ,  := range .Credentials {
		// Filter out configuration entries
		if strings.HasPrefix(.Server.Realm, "X-CACHECONF") {
			continue
		}
		 = append(, )
	}
	return 
}

func ( *headerField) () bool {
	// See https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html - Header format
	switch .tag {
	case headerFieldTagKDCOffset:
		if .length != 8 || len(.value) != 8 {
			return false
		}
		return true
	}
	return false
}

func readData( []byte,  *int,  *binary.ByteOrder) []byte {
	 := readInt32(, , )
	return readBytes(, , int(), )
}

func readAddress( []byte,  *int,  *binary.ByteOrder) types.HostAddress {
	 := types.HostAddress{}
	.AddrType = int32(readInt16(, , ))
	.Address = readData(, , )
	return 
}

func readAuthDataEntry( []byte,  *int,  *binary.ByteOrder) types.AuthorizationDataEntry {
	 := types.AuthorizationDataEntry{}
	.ADType = int32(readInt16(, , ))
	.ADData = readData(, , )
	return 
}

// Read bytes representing a timestamp.
func readTimestamp( []byte,  *int,  *binary.ByteOrder) time.Time {
	return time.Unix(int64(readInt32(, , )), 0)
}

// Read bytes representing an eight bit integer.
func readInt8( []byte,  *int,  *binary.ByteOrder) ( int8) {
	 := bytes.NewBuffer([* : *+1])
	binary.Read(, *, &)
	*++
	return
}

// Read bytes representing a sixteen bit integer.
func readInt16( []byte,  *int,  *binary.ByteOrder) ( int16) {
	 := bytes.NewBuffer([* : *+2])
	binary.Read(, *, &)
	* += 2
	return
}

// Read bytes representing a thirty two bit integer.
func readInt32( []byte,  *int,  *binary.ByteOrder) ( int32) {
	 := bytes.NewBuffer([* : *+4])
	binary.Read(, *, &)
	* += 4
	return
}

func readBytes( []byte,  *int,  int,  *binary.ByteOrder) []byte {
	 := bytes.NewBuffer([* : *+])
	 := make([]byte, )
	binary.Read(, *, &)
	* += 
	return 
}

func isNativeEndianLittle() bool {
	var  = 0x012345678
	var  = unsafe.Pointer(&)
	var  = (*[4]byte)()

	var  bool
	if 0x01 == [0] {
		 = false
	} else if (0x78 & 0xff) == ([0] & 0xff) {
		 = true
	} else {
		// Default to big endian
		 = false
	}
	return 
}