# -*- coding: binary -*-

require 'bindata'

# Models for or Krb5 keytab
module Rex::Proto::Kerberos::Keytab
  class Krb5KeytabCountedOctetString < BinData::Primitive
    endian :big
    search_prefix :krb5_keytab

    # @!attribute [rw] len
    #   @return [Integer]
    uint16 :len, value: -> { data.length }

    # @!attribute [rw] data
    #   @return [String]
    string :data, read_length: :len

    def get
      data.snapshot
    end

    def set(v)
      self.data = v
    end
  end

  class Krb5KeytabKeyblock < BinData::Record
    endian :big
    search_prefix :krb5_keytab

    # @!attribute [rw] enctype
    #   @return [Integer] The encryption type
    # @see Rex::Proto::Kerberos::Crypto::Encryption
    uint16 :enctype

    # @return [KeytabCountedOctetString]
    counted_octet_string :data
  end

  class Krb5KeytabEpoch < BinData::Primitive
    endian :big
    search_prefix :krb5_keytab

    # @!attribute [rw] epoch
    #   @return [Integer]
    uint32 :epoch

    def get
      Time.at(epoch)
    end

    def set(v)
      self.epoch = v.to_i
    end
  end

  class Krb5KeytabEntry < BinData::Record
    endian :big
    search_prefix :krb5_keytab

    # @return [Integer] The number of bytes for the len field
    LEN_FIELD_BYTE_SIZE = 4

    # @!attribute [rw] len
    #   @return [Integer] The number of remaining bytes for this record. The length does not include the 4 bytes for this field
    int32 :len,
          value: -> {
            size = [
              count_of_components,
              realm,
              components,
              name_type,
              timestamp,
              vno8,
              keyblock,
              vno,
              flags
            ].sum { |field| field.to_binary_s.bytes.count }

            size
         }

    # @!attribute [rw] count_of_components
    #   @return [Integer]
    uint16 :count_of_components, value: -> { components.length }

    # @!attribute [rw] realm#
    #   @return [CountedOctetString]
    counted_octet_string :realm

    # @!attribute [rw] components
    #   @return [Array<CountedOctetString>] The components in the principal name, which can be joined by slashes
    #     to represent the SPN
    array :components, initial_length: :count_of_components, type: :counted_octet_string

    # @!attribute [rw] name_type
    #   @return [Integer]
    # @see Rex::Proto::Kerberos::Model::NameType
    uint32 :name_type

    # @!attribute [rw] timestamp
    #   @return [Integer] The time the key entry was created; Can be 0 for keytabs generated by ktpass
    epoch :timestamp

    # @!attribute [rw] vno8
    #   @return [Integer] The lower 8 bits of the version number of the key
    uint8 :vno8

    # @!attribute [rw] keyblock
    #   @return [KeytabKeyBlock]
    keyblock :keyblock

    # @!attribute [rw] vno
    #   @return [Integer]
    uint32 :vno,
           initial_value: -> { vno8.to_i },
           # only present if >= 4 bytes left in entry
           onlyif: -> { ((len + LEN_FIELD_BYTE_SIZE) - vno.rel_offset) >= 4 }

    # @!attribute [rw] flags
    #   @return [Integer]
    uint32 :flags,
           # only present if >= 4 bytes left in entry
           onlyif: -> { ((len + LEN_FIELD_BYTE_SIZE) - flags.rel_offset) >= 4 }

    # @return [String] The principal associated with this key tab entry
    def principal
      "#{components.to_a.join('/')}@#{realm}"
    end
  end

  # Definition from:
  # https://web.mit.edu/kerberos/krb5-devel/doc/basic/keytab_def.html
  # http://www.ioplex.com/utilities/keytab.txt
  # http://web.mit.edu/freebsd/head/crypto/heimdal/doc/doxyout/krb5/html/krb5_fileformats.html
  #
  class Krb5Keytab < BinData::Record
    endian :big
    search_prefix :krb5_keytab

    # Older keytab version 0x501 not currently supported
    # @!attribute [r] file_format_version
    #   @return [Integer]
    uint16 :file_format_version, asserted_value: 0x502

    # @!attribute [rw] key_entries
    #   @return [Array<KeytabEntry>] the keytab entries
    array :key_entries, type: :entry, read_until: :eof
  end
end
