=begin
Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>

Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php
=end
require 'gssapi/lib_gssapi_loader'
module GSSAPI
  module LibGSSAPI

    # Libc functions

    # void *malloc(size_t size);
    attach_function :malloc, [:size_t], :pointer

    # void *memcpy(void *dest, const void *src, size_t n);
    attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer

    class GssOID < FFI::Struct
      layout  :length   =>  :OM_uint32,
        :elements => :pointer # pointer of :void

      def self.gss_c_no_oid
        self.new(GSSAPI::LibGSSAPI::GSS_C_NO_OID)
      end
    end

    # This is a generic Managed Struct subclass that hides the [] methods.
    # Classes that implement this class should provide accessor methods to get to the attributes.
    class GssMStruct < FFI::ManagedStruct
      private

      def [](key)
        super(key)
      end

      def []=(key,val)
        super(key,val)
      end
    end

    # This is a generic Unmanaged Struct subclass that hides the [] methods.
    # Classes that implement this class should provide accessor methods to get to the attributes.
    class GssUMStruct < FFI::Struct
      private

      def [](key)
        super(key)
      end

      def []=(key,val)
        super(key,val)
      end
    end

    # This module provides a layout for both the managed and unmanaged GssBufferDesc structs.
    module GssBufferDescLayout
      def self.included(base)
        base.class_eval do
          layout :length => :OM_uint32,
            :value => :pointer # pointer of :void

          def length
            self[:length]
          end

          def value
            if(self[:length] == 0)
              nil
            else
              self[:value].read_string(self[:length])
            end
          end
        end
      end
    end

    # This class implements the gss_buffer_desc type.  Use #pointer to emulate gss_buffer_t
    # If you are setting the value of the buffer and it is not being set from the function
    # this is the type of buffer you should use. If the buffer is being allocated and set
    # inside the function you should use a ManagedGssBufferDesc instead so gss_release_buffer
    # is called for it. It states in the manpage for each gss function whether or not
    # gss_release_buffer needs to be called or not.
    # @example
    #   buff = UnManagedGssBufferDesc.new
    #   buff.value = "This is a test"
    class UnManagedGssBufferDesc < GssUMStruct
      include GssBufferDescLayout
      def initialize(ptr = nil)
        if(ptr.nil?)
          super(FFI::Pointer.new(FFI::MemoryPointer.new(self.size)))
        else
          super(ptr)
        end
      end

      # Set the value of the string for the "value" parameter.  This method also
      #   appropriately sets the length parameter.
      def value=(val)
        if(val.nil?)
          self[:length] = 0
          self[:value] = val
        elsif(val.is_a?(String))
          buff = FFI::MemoryPointer.from_string(val)
          self[:length] = val.length
          self[:value] = buff
        elsif(val.is_a?(Fixnum))
          buff = FFI::MemoryPointer.new :OM_uint32
          buff.write_int val
          self[:length] = FFI::type_size :OM_uint32
          self[:value] = buff
        else
          raise StandardError, "Can't handle type #{val.class.name}"
        end
      end
    end

    # This class implements the gss_buffer_desc type.  Use #pointer to emulate gss_buffer_t
    # Only functions that need to call gss_release_buffer should use this type. It states
    # in the manpage for each function whether or not it should be called. If it does not
    # you should be using UnManagedGssBufferDesc instead.
    class ManagedGssBufferDesc < GssMStruct
      include GssBufferDescLayout
      def initialize(ptr = nil)
        if(ptr.nil?)
          super(FFI::Pointer.new(FFI::MemoryPointer.new(self.size)))
        else
          super(ptr)
        end
      end

      def self.release(ptr)
        puts "Releasing ManagedGssBufferDesc at #{ptr.address.to_s(16)}" if $DEBUG
        min_stat = FFI::MemoryPointer.new :OM_uint32
        maj_stat = LibGSSAPI.gss_release_buffer(min_stat, ptr)
      end
    end

    # @example
    #   iov_buff = GssIOVBufferDesc.new
    #   str = FFI::MemoryPointer.from_string("This is the string")
    #   iov_buff[:type] = 1
    #   iov_buff[:buffer][:length] = str.size
    #   iov_buff[:buffer][:value] = str
    class GssIOVBufferDesc < FFI::Struct
      layout  :type   => :OM_uint32,
              :buffer => UnManagedGssBufferDesc
    end

    class GssChannelBindingsStruct < FFI::Struct
      layout  :initiator_addrtype => :OM_uint32,
        :initiator_address  => UnManagedGssBufferDesc,
        :acceptor_addrtype  => :OM_uint32,
        :acceptor_address   => UnManagedGssBufferDesc,
        :application_data   => UnManagedGssBufferDesc

      no_chn_bind = FFI::MemoryPointer.new :pointer  #
      no_chn_bind.write_int 0
    end

    # This s a generic AutoPointer.  Gss pointers that implement this class should also implement a
    #   class method called release_ptr that releases the structure pointed to by this pointer.
    class GssPointer < FFI::AutoPointer
      def address_of
        ptr_p = FFI::MemoryPointer.new :pointer
        ptr_p.write_pointer(self)
      end

      def self.release(ptr)
        if( ptr.address == 0 )
          puts "NULL POINTER: Not freeing" if $DEBUG
          return
        else
          puts "Releasing #{self.name} at #{ptr.address.to_s(16)}" if $DEBUG
          self.release_ptr(ptr)
        end
      end
    end

    # A wrapper around gss_name_t so that it garbage collects
    class GssNameT < GssPointer
      def self.release_ptr(name_ptr)
        puts "Releasing gss_name_t at #{name_ptr.address.to_s(16)}" if $DEBUG
        min_stat = FFI::MemoryPointer.new :OM_uint32
        maj_stat = LibGSSAPI.gss_release_name(min_stat, name_ptr)
      end
    end

    class GssCtxIdT < GssPointer
      def self.release_ptr(context_ptr)
        min_stat = FFI::MemoryPointer.new :OM_uint32
        maj_stat = LibGSSAPI.gss_delete_sec_context(min_stat, context_ptr, LibGSSAPI::GSS_C_NO_BUFFER)
      end

      def self.gss_c_no_context
        self.new(GSSAPI::LibGSSAPI::GSS_C_NO_CONTEXT)
      end
    end

    # gss_cred_id_t
    class GssCredIdT < GssPointer
      def self.release_ptr(cred_ptr)
        min_stat = FFI::MemoryPointer.new :OM_uint32
        maj_stat = LibGSSAPI.gss_release_cred(min_stat, cred_ptr)
      end
    end


    # OM_uint32 gss_release_buffer(OM_uint32 * minor_status, gss_buffer_t buffer);

    # Function definitions
    # --------------------

    # OM_uint32 gss_import_name(OM_uint32 * minor_status, const gss_buffer_t input_name_buffer, const gss_OID input_name_type, gss_name_t * output_name);
    # @example:
    #   host_str = 'host@example.com'
    #   buff_str = GSSAPI::LibGSSAPI::UnManagedGssBufferDesc.new
    #   buff_str[:length] = host_str.length
    #   buff_str[:value] = FFI::MemoryPointer.from_string(host_str)
    #   name = FFI::MemoryPointer.new :pointer # gss_name_t
    #   min_stat = FFI::MemoryPointer.new :OM_uint32
    #   maj_stat = GSSAPI::LibGSSAPI.gss_import_name(min_stat, buff_str.pointer, GSSAPI::LibGSSAPI.GSS_C_NT_HOSTBASED_SERVICE, name)
    #   name = name.get_pointer(0)
    # Remember to free the allocated name (gss_name_t) space with gss_release_name
    attach_function :gss_import_name, [:pointer, :pointer, :pointer, :pointer], :OM_uint32

    # OM_uint32 gss_export_name(OM_uint32 * minor_status, const gss_name_t input_name, gss_buffer_t exported_name);
    attach_function :gss_export_name, [:pointer, :pointer, :pointer], :OM_uint32

    # OM_uint32 gss_canonicalize_name(OM_uint32 * minor_status, const gss_name_t input_name, const gss_OID mech_type, gss_name_t * output_name)
    attach_function :gss_canonicalize_name, [:pointer, :pointer, :pointer, :pointer], :OM_uint32

    begin
      # OM_uint32 gss_oid_to_str(OM_uint32 *minor_status, const gss_OID oid, gss_buffer_t oid_str);
      # @example:
      #   min_stat = FFI::MemoryPointer.new :OM_uint32
      #   oidstr = GSSAPI::LibGSSAPI::ManagedGssBufferDesc.new
      #   maj_stat = GSSAPI::LibGSSAPI.gss_oid_to_str(min_stat, GSSAPI::LibGSSAPI.GSS_C_NT_HOSTBASED_SERVICE, oidstr.pointer)
      #   oidstr[:value].read_string
      attach_function :gss_oid_to_str, [:pointer, :pointer, :pointer], :OM_uint32

      # OM_uint32 gss_str_to_oid(OM_uint32 *minor_status, const gss_buffer_t oid_str, gss_OID *oid);
      # @example: Simulate GSS_C_NT_HOSTBASED_SERVICE
      #   min_stat = FFI::MemoryPointer.new :OM_uint32
      #   str = "{ 1 2 840 113554 1 2 1 4 }"
      #   oidstr = GSSAPI::LibGSSAPI::UnManagedGssBufferDesc.new
      #   oidstr[:length] = str.length
      #   oidstr[:value] = FFI::MemoryPointer.from_string str
      #   oid = FFI::MemoryPointer.new :pointer
      #   min_stat = FFI::MemoryPointer.new :OM_uint32
      #   maj_stat = GSSAPI::LibGSSAPI.gss_str_to_oid(min_stat, oidstr.pointer, oid)
      #   oid = GSSAPI::LibGSSAPI::GssOID.new(oid.get_pointer(0))
      attach_function :gss_str_to_oid, [:pointer, :pointer, :pointer], :OM_uint32
    rescue FFI::NotFoundError => ex
      warn "WARNING: Could not load OID conversion methods. Check your GSSAPI C library for an update"
    end

    # OM_uint32  gss_init_sec_context(OM_uint32  *  minor_status, const gss_cred_id_t initiator_cred_handle,
    #   gss_ctx_id_t * context_handle, const gss_name_t target_name, const gss_OID mech_type, OM_uint32 req_flags,
    #   OM_uint32 time_req, const gss_channel_bindings_t input_chan_bindings, const gss_buffer_t input_token,
    #   gss_OID * actual_mech_type, gss_buffer_t output_token, OM_uint32 * ret_flags, OM_uint32 * time_rec);
    attach_function :gss_init_sec_context, [:pointer, :pointer, :pointer, :pointer, :pointer, :OM_uint32, :OM_uint32, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :OM_uint32

    # OM_uint32 gss_accept_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle, const gss_cred_id_t acceptor_cred_handle,
    #   const gss_buffer_t input_token_buffer, const gss_channel_bindings_t input_chan_bindings, gss_name_t *src_name, gss_OID *mech_type,
    #   gss_buffer_t output_token, OM_uint32 *ret_flags, OM_uint32 *time_rec, gss_cred_id_t *delegated_cred_handle);
    attach_function :gss_accept_sec_context, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :OM_uint32

    # OM_uint32 gss_acquire_cred(OM_uint32 *minor_status, const gss_name_t desired_name, OM_uint32 time_req, const gss_OID_set desired_mechs,
    #   gss_cred_usage_t cred_usage, gss_cred_id_t *output_cred_handle, gss_OID_set *actual_mechs, OM_uint32 *time_rec);
    attach_function :gss_acquire_cred, [:pointer, :pointer, :OM_uint32, :pointer, :OM_uint32, :pointer, :pointer, :pointer], :OM_uint32

    # OM_uint32  gss_wrap(OM_uint32  *  minor_status, const gss_ctx_id_t context_handle, int conf_req_flag,
    #   gss_qop_t qop_req, const gss_buffer_t input_message_buffer, int * conf_state, gss_buffer_t output_message_buffer);
    # @example:
    #   min_stat = FFI::MemoryPointer.new :OM_uint32
    # Remember to free the allocated output_message_buffer with gss_release_buffer
    attach_function :gss_wrap, [:pointer, :pointer, :int, :OM_uint32, :pointer, :pointer, :pointer], :OM_uint32
    
    # Some versions of GSSAPI might not have support for IOV yet.
    begin
      # OM_uint32 GSSAPI_LIB_FUNCTION gss_wrap_iov( OM_uint32 * minor_status, gss_ctx_id_t  context_handle,
      #   int conf_req_flag, gss_qop_t  qop_req, int *  conf_state, gss_iov_buffer_desc *  iov, int  iov_count );
      attach_function :gss_wrap_iov, [:pointer, :pointer, :int, :OM_uint32, :pointer, :pointer, :int], :OM_uint32

      # OM_uint32 GSSAPI_LIB_FUNCTION gss_unwrap_iov ( OM_uint32 * minor_status, gss_ctx_id_t context_handle,
      #   int * conf_state, gss_qop_t * qop_state, gss_iov_buffer_desc * iov, int iov_count )
      attach_function :gss_unwrap_iov, [:pointer, :pointer, :pointer, :pointer, :pointer, :int], :OM_uint32

      # OM_uint32 GSSAPI_LIB_CALL gss_wrap_iov_length ( OM_uint32 * minor_status, gss_ctx_id_t context_handle,
      #   int conf_req_flag, gss_qop_t  qop_req, int *  conf_state, gss_iov_buffer_desc * iov, int iov_count)
      attach_function :gss_wrap_iov_length, [:pointer, :pointer, :int, :OM_uint32, :pointer, :pointer, :int], :OM_uint32
    rescue FFI::NotFoundError => ex
      warn "WARNING: Could not load IOV methods. Check your GSSAPI C library for an update"
    end
    
    begin
      # OM_uint32 gss_wrap_aead(OM_uint32 * minor_status, gss_ctx_id_t context_handle, int conf_req_flag,
      #   gss_qop_t qop_req, gss_buffer_t input_assoc_buffer,
      #   gss_buffer_t input_payload_buffer, int * conf_state, gss_buffer_t output_message_buffer);
      attach_function :gss_wrap_aead, [:pointer, :pointer, :int, :OM_uint32, :pointer, :pointer, :pointer, :pointer], :OM_uint32

      # OM_uint32 gss_unwrap_aead(OM_uint32 * minor_status, gss_ctx_id_t context_handle, gss_buffer_t input_message_buffer,
      #   gss_buffer_t input_assoc_buffer, gss_buffer_t output_payload_buffer, int * conf_state, gss_qop_t * qop_state);
      attach_function :gss_unwrap_aead, [:pointer,:pointer,:pointer,:pointer,:pointer,:pointer,:pointer], :OM_uint32
    rescue FFI::NotFoundError => ex
      warn "WARNING: Could not load AEAD methods. Check your GSSAPI C library for an update"
    end

    # OM_uint32  gss_unwrap(OM_uint32  *  minor_status, const gss_ctx_id_t context_handle,
    #   const gss_buffer_t input_message_buffer, gss_buffer_t output_message_buffer, int * conf_state, gss_qop_t * qop_state);
    # @example:
    #   min_stat = FFI::MemoryPointer.new :OM_uint32
    # Remember to free the allocated output_message_buffer with gss_release_buffer
    attach_function :gss_unwrap, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :OM_uint32

    # OM_uint32 gss_delete_sec_context(OM_uint32 * minor_status, gss_ctx_id_t * context_handle, gss_buffer_t output_token);
    attach_function :gss_delete_sec_context, [:pointer, :pointer, :pointer], :OM_uint32

    # OM_uint32 gss_release_name(OM_uint32 * minor_status, gss_name_t * name);
    attach_function :gss_release_name, [:pointer, :pointer], :OM_uint32

    # OM_uint32 gss_release_buffer(OM_uint32 * minor_status, gss_buffer_t buffer);
    attach_function :gss_release_buffer, [:pointer, :pointer], :OM_uint32

    # OM_uint32 gss_release_cred(OM_uint32 *minor_status, gss_cred_id_t *cred_handle);
    attach_function :gss_release_cred, [:pointer, :pointer], :OM_uint32

    # Used to register alternate keytabs
    # OM_uint32 krb5_gss_register_acceptor_identity(const char *);
    attach_function :krb5_gss_register_acceptor_identity, [:string], :OM_uint32

    # OM_uint32 gss_display_status(OM_uint32 *minor_status, OM_uint32 status_value, int status_type, gss_OID mech_type, OM_uint32 *message_context, gss_buffer_t status_string)
    attach_function :gss_display_status, [:pointer, :OM_uint32, :int, :pointer, :pointer, :pointer], :OM_uint32

    # Variable definitions
    # --------------------

    attach_variable :GSS_C_NT_HOSTBASED_SERVICE, :pointer # type gss_OID
    attach_variable :GSS_C_NT_EXPORT_NAME, :pointer # type gss_OID

    # Flag bits for context-level services.
    GSS_C_DELEG_FLAG        = 1
    GSS_C_MUTUAL_FLAG       = 2
    GSS_C_REPLAY_FLAG       = 4
    GSS_C_SEQUENCE_FLAG     = 8
    GSS_C_CONF_FLAG         = 16
    GSS_C_INTEG_FLAG        = 32
    GSS_C_ANON_FLAG         = 64
    GSS_C_PROT_READY_FLAG   = 128
    GSS_C_TRANS_FLAG        = 256
    GSS_C_DELEG_POLICY_FLAG = 32768


    # Credential usage options
    GSS_C_BOTH      = 0
    GSS_C_INITIATE  = 1
    GSS_C_ACCEPT    = 2

    # Misc Constants
    GSS_C_INDEFINITE = 0xffffffff # Expiration time of 2^32-1 seconds means infinite lifetime for sec or cred context

    # Message Offsets
    GSS_C_CALLING_ERROR_OFFSET = 24
    GSS_C_ROUTINE_ERROR_OFFSET = 16
    GSS_C_SUPPLEMENTARY_OFFSET = 0
    # GSS_C_CALLING_ERROR_MASK ((OM_uint32) 0377ul)
    # GSS_C_ROUTINE_ERROR_MASK ((OM_uint32) 0377ul)
    # GSS_C_SUPPLEMENTARY_MASK ((OM_uint32) 0177777ul)


    # QOP (Quality of Protection)
    GSS_C_QOP_DEFAULT       = 0


    # GSSAPI Status & Error Codes
    GSS_S_COMPLETE  = 0
    GSS_C_GSS_CODE  = 1
    GSS_C_MECH_CODE = 2

    GSS_C_CALLING_ERRORS = {
      (1 << GSS_C_CALLING_ERROR_OFFSET) => "GSS_S_CALL_INACCESSIBLE_READ",
      (2 << GSS_C_CALLING_ERROR_OFFSET) => "GSS_S_CALL_INACCESSIBLE_WRITE",
      (3 << GSS_C_CALLING_ERROR_OFFSET) => "GSS_S_CALL_BAD_STRUCTURE"
    }

    GSS_C_SUPPLEMENTARY_CODES = {
      (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 0)) => "GSS_S_CONTINUE_NEEDED",
      (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 1)) => "GSS_S_DUPLICATE_TOKEN",
      (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 2)) => "GSS_S_OLD_TOKEN",
      (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 3)) => "GSS_S_UNSEQ_TOKEN",
      (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 4)) => "GSS_S_GAP_TOKEN"
    }

    GSS_C_ROUTINE_ERRORS = {
      (1 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_BAD_MECH",
      (2 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_BAD_NAME",
      (3 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_BAD_NAMETYPE",
      (4 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_BAD_BINDINGS",
      (5 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_BAD_STATUS",
      (6 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_BAD_SIG",
      (7 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_NO_CRED",
      (8 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_NO_CONTEXT",
      (9 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_DEFECTIVE_TOKEN",
      (10 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_DEFECTIVE_CREDENTIAL",
      (11 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_CREDENTIALS_EXPIRED",
      (12 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_CONTEXT_EXPIRED",
      (13 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_FAILURE",
      (14 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_BAD_QOP",
      (15 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_UNAUTHORIZED",
      (16 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_UNAVAILABLE",
      (17 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_DUPLICATE_ELEMENT",
      (18 << GSS_C_ROUTINE_ERROR_OFFSET) => "GSS_S_NAME_NOT_MN"
    }


    # IOV Buffer Types (gssapi_ext.h)
    GSS_IOV_BUFFER_TYPE_EMPTY       = 0
    GSS_IOV_BUFFER_TYPE_DATA        = 1 # Packet data
    GSS_IOV_BUFFER_TYPE_HEADER      = 2 # Mechanism header
    GSS_IOV_BUFFER_TYPE_MECH_PARAMS = 3 # Mechanism specific parameters
    GSS_IOV_BUFFER_TYPE_TRAILER     = 7 # Mechanism trailer
    GSS_IOV_BUFFER_TYPE_PADDING     = 9  # Padding
    GSS_IOV_BUFFER_TYPE_STREAM      = 10 # Complete wrap token
    GSS_IOV_BUFFER_TYPE_SIGN_ONLY   = 11 # Sign only packet data
    # Flags
    GSS_IOV_BUFFER_FLAG_MASK        = 0xFFFF0000
    GSS_IOV_BUFFER_FLAG_ALLOCATE    = 0x00010000 # indicates GSS should allocate
    GSS_IOV_BUFFER_FLAG_ALLOCATED   = 0x00020000 # indicates caller should free



    # Various Null values. (gssapi.h)
    GSS_C_NO_NAME           = FFI::Pointer.new(:pointer, 0) # ((gss_name_t) 0)
    GSS_C_NO_BUFFER         = FFI::Pointer.new(:pointer, 0) # ((gss_buffer_t) 0)
    GSS_C_NO_OID            = FFI::Pointer.new(:pointer, 0) # ((gss_OID) 0)
    GSS_C_NO_OID_SET        = FFI::Pointer.new(:pointer, 0) # ((gss_OID_set) 0)
    GSS_C_NO_CONTEXT        = FFI::Pointer.new(:pointer, 0) # ((gss_ctx_id_t) 0)
    GSS_C_NO_CREDENTIAL     = FFI::Pointer.new(:pointer, 0) # ((gss_cred_id_t) 0)
    GSS_C_NO_CHANNEL_BINDINGS = FFI::Pointer.new(:pointer, 0) # ((gss_channel_bindings_t) 0)
    GSS_C_EMPTY_BUFFER      = ManagedGssBufferDesc.new

  end #end LibGSSAPI
end #end GSSAPI
